From: Tony Tkacik Date: Mon, 23 Feb 2015 16:08:58 +0000 (+0000) Subject: Merge "Cleanup root pom "name"." X-Git-Tag: release/lithium~470^2~6 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=bfd413d87f82ee3ffed67a141a980805950a0f06;hp=87c52f1bb7b934d90b9bdf57a4865ebef00043ff Merge "Cleanup root pom "name"." --- diff --git a/features/mdsal/pom.xml b/features/mdsal/pom.xml index 5e6afd248f..5559f92775 100644 --- a/features/mdsal/pom.xml +++ b/features/mdsal/pom.xml @@ -113,6 +113,12 @@ features xml + + org.opendaylight.controller + features-netconf + features + xml + org.opendaylight.controller features-config-persister @@ -240,6 +246,15 @@ sal-inmemory-datastore + + org.opendaylight.controller + mdsal-netconf-connector + + + org.opendaylight.controller + mdsal-netconf-monitoring + + org.opendaylight.controller sal-netconf-connector diff --git a/features/mdsal/src/main/resources/features.xml b/features/mdsal/src/main/resources/features.xml index 1582f45789..4b9f8c2288 100644 --- a/features/mdsal/src/main/resources/features.xml +++ b/features/mdsal/src/main/resources/features.xml @@ -7,6 +7,7 @@ mvn:org.opendaylight.controller/features-config/${config.version}/xml/features mvn:org.opendaylight.controller/features-config-persister/${config.version}/xml/features mvn:org.opendaylight.controller/features-config-netty/${config.version}/xml/features + mvn:org.opendaylight.controller/features-netconf/${netconf.version}/xml/features mvn:org.opendaylight.controller/features-akka/${commons.opendaylight.version}/xml/features odl-mdsal-broker @@ -21,6 +22,22 @@ mvn:org.opendaylight.controller/sal-common-impl/${mdsal.version} mvn:org.opendaylight.controller/sal-common-util/${mdsal.version} + + + + + odl-config-all + odl-netconf-all + mvn:org.opendaylight.controller/netconf-ssh/${netconf.version} + odl-mdsal-broker + mvn:org.opendaylight.controller/mdsal-netconf-connector/${netconf.version} + mvn:org.opendaylight.controller/mdsal-netconf-monitoring/${netconf.version} + + mvn:org.opendaylight.controller/netconf-config-dispatcher/${config.version} + mvn:org.opendaylight.controller/netconf-config/${netconf.version}/xml/config + mvn:org.opendaylight.controller/netconf-mdsal-config/${netconf.version}/xml/config + + odl-yangtools-common odl-yangtools-binding @@ -28,6 +45,7 @@ odl-mdsal-common odl-config-startup odl-config-netty + mvn:com.lmax/disruptor/${lmax.version} mvn:org.opendaylight.controller/sal-core-api/${project.version} mvn:org.opendaylight.controller/sal-core-spi/${project.version} mvn:org.opendaylight.controller/sal-broker-impl/${project.version} diff --git a/features/netconf-connector/src/main/resources/features.xml b/features/netconf-connector/src/main/resources/features.xml index 863833bcc0..92e6507d21 100644 --- a/features/netconf-connector/src/main/resources/features.xml +++ b/features/netconf-connector/src/main/resources/features.xml @@ -42,6 +42,8 @@ --> odl-netconf-connector odl-netconf-connector-ssh + + + + org.opendaylight.yangtools + features-test + ${yangtools.version} + test + + + + org.opendaylight.controller + opendaylight-karaf-empty + ${commons.opendaylight.version} + zip + @@ -169,6 +207,21 @@ + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + org.opendaylight.controller + opendaylight-karaf-empty + ${commons.opendaylight.version} + + + org.opendaylight.yangtools:features-test + + + diff --git a/features/netconf/src/main/resources/features.xml b/features/netconf/src/main/resources/features.xml index 9de15630c3..aa8287d709 100644 --- a/features/netconf/src/main/resources/features.xml +++ b/features/netconf/src/main/resources/features.xml @@ -22,9 +22,12 @@ mvn:org.opendaylight.controller/netconf-api/${project.version} mvn:org.opendaylight.controller/netconf-auth/${project.version} mvn:org.opendaylight.controller/ietf-netconf-monitoring/${project.version} + mvn:org.opendaylight.controller/ietf-netconf/${project.version} + mvn:org.opendaylight.controller/ietf-netconf-notifications/${project.version} mvn:org.opendaylight.controller/ietf-netconf-monitoring-extension/${project.version} mvn:org.opendaylight.yangtools.model/ietf-inet-types/${ietf-inet-types.version} mvn:org.opendaylight.yangtools.model/ietf-yang-types/${ietf-yang-types.version} + mvn:org.opendaylight.yangtools.model/ietf-yang-types-20130715/2013.07.15.7-SNAPSHOT odl-netconf-api @@ -43,6 +46,7 @@ odl-config-netconf-connector odl-netconf-monitoring + odl-netconf-notifications-impl mvn:org.opendaylight.controller/netconf-impl/${project.version} @@ -50,6 +54,7 @@ odl-netconf-api odl-netconf-mapping-api odl-netconf-util + odl-netconf-notifications-api mvn:org.opendaylight.controller/config-netconf-connector/${project.version} @@ -75,5 +80,15 @@ odl-netconf-util mvn:org.opendaylight.controller/netconf-monitoring/${project.version} + + odl-netconf-api + mvn:org.opendaylight.controller/netconf-notifications-api/${project.version} + + + odl-netconf-notifications-api + odl-netconf-util + odl-yangtools-binding-generator + mvn:org.opendaylight.controller/netconf-notifications-impl/${project.version} + diff --git a/features/pom.xml b/features/pom.xml index ed4fa499a9..9363d9e03b 100644 --- a/features/pom.xml +++ b/features/pom.xml @@ -22,6 +22,5 @@ netconf-connector restconf extras - neutron diff --git a/itests/base-features-it/pom.xml b/itests/base-features-it/pom.xml index d05e9a515b..dfb622eb7c 100644 --- a/itests/base-features-it/pom.xml +++ b/itests/base-features-it/pom.xml @@ -25,7 +25,7 @@ org.ops4j.pax.exam pax-exam-container-karaf - ${pax.exam.version} + ${exam.version} test @@ -36,7 +36,7 @@ org.ops4j.pax.exam pax-exam - ${pax.exam.version} + ${exam.version} test diff --git a/karaf/karaf-parent/pom.xml b/karaf/karaf-parent/pom.xml index 06d8c8d99b..baf67302e0 100644 --- a/karaf/karaf-parent/pom.xml +++ b/karaf/karaf-parent/pom.xml @@ -19,9 +19,7 @@ and is available at http://www.eclipse.org/legal/epl-v10.html karaf-parent ${project.artifactId} pom - - 3.1.1 - + 1.1.0-SNAPSHOT 1.5.0-SNAPSHOT @@ -320,6 +318,26 @@ and is available at http://www.eclipse.org/legal/epl-v10.html + + org.apache.maven.plugins + maven-enforcer-plugin + ${enforcer.version} + + + enforce-maven + + enforce + + + + + 3.1.1 + + + + + + maven-resources-plugin diff --git a/karaf/opendaylight-karaf-empty/pom.xml b/karaf/opendaylight-karaf-empty/pom.xml index a13023cbee..aa772096cd 100644 --- a/karaf/opendaylight-karaf-empty/pom.xml +++ b/karaf/opendaylight-karaf-empty/pom.xml @@ -9,9 +9,6 @@ opendaylight-karaf-empty pom - - 3.0 - diff --git a/karaf/opendaylight-karaf-resources/src/main/resources/configuration/tomcat-server.xml b/karaf/opendaylight-karaf-resources/src/main/resources/configuration/tomcat-server.xml index fbcd0a4c77..5092a5eee1 100644 --- a/karaf/opendaylight-karaf-resources/src/main/resources/configuration/tomcat-server.xml +++ b/karaf/opendaylight-karaf-resources/src/main/resources/configuration/tomcat-server.xml @@ -26,9 +26,9 @@ - + redirectPort="8663" /> + + + + + + + + + + + + + + + + + + + + + 300000 + 2 + false + 8543 + 20000 + 5000 + + + + + + + + + + + + + 300000 + 2 + false + 8443 + 20000 + 5000 + + + + + + + + + + + + + + + karaf + karaf + + + org.apache.karaf.jaas.boot.principal.RolePrincipal + + + + + + + + + + default + karaf + + + org.apache.karaf.jaas.boot.principal.RolePrincipal + + + + + + + + diff --git a/karaf/opendaylight-karaf/pom.xml b/karaf/opendaylight-karaf/pom.xml index e0ea4c0edf..3b29fa276e 100644 --- a/karaf/opendaylight-karaf/pom.xml +++ b/karaf/opendaylight-karaf/pom.xml @@ -9,9 +9,6 @@ distribution.opendaylight-karaf pom - - 3.0 - @@ -121,6 +118,14 @@ xml + + org.opendaylight.controller + features-neutron + features + xml + runtime + + diff --git a/opendaylight/adsal/adsal-enunciate-parent/enunciate.xml b/opendaylight/adsal/adsal-enunciate-parent/enunciate.xml new file mode 100644 index 0000000000..431ea7b316 --- /dev/null +++ b/opendaylight/adsal/adsal-enunciate-parent/enunciate.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/opendaylight/adsal/adsal-enunciate-parent/pom.xml b/opendaylight/adsal/adsal-enunciate-parent/pom.xml new file mode 100644 index 0000000000..11e3667079 --- /dev/null +++ b/opendaylight/adsal/adsal-enunciate-parent/pom.xml @@ -0,0 +1,42 @@ + + + + + + + org.opendaylight.controller + enunciate-parent + 1.5.0-SNAPSHOT + ../../commons/enunciate-parent + + + 4.0.0 + adsal-enunciate-parent + pom + + + + + + org.codehaus.enunciate + maven-enunciate-plugin + + + org.opendaylight.controller + sal + ${sal.version} + + + + + + + + diff --git a/opendaylight/adsal/northbound/connectionmanager/pom.xml b/opendaylight/adsal/northbound/connectionmanager/pom.xml index b68bc25545..7cc12484f2 100644 --- a/opendaylight/adsal/northbound/connectionmanager/pom.xml +++ b/opendaylight/adsal/northbound/connectionmanager/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../adsal-enunciate-parent connectionmanager.northbound @@ -75,17 +75,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/adsal/northbound/containermanager/pom.xml b/opendaylight/adsal/northbound/containermanager/pom.xml index 8898a06f64..1bc170a679 100644 --- a/opendaylight/adsal/northbound/containermanager/pom.xml +++ b/opendaylight/adsal/northbound/containermanager/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../../commons/enunciate-parent containermanager.northbound @@ -73,10 +73,6 @@ - - org.codehaus.enunciate - maven-enunciate-plugin - diff --git a/opendaylight/adsal/northbound/controllermanager/pom.xml b/opendaylight/adsal/northbound/controllermanager/pom.xml index 395dd2dc29..20cff42b82 100644 --- a/opendaylight/adsal/northbound/controllermanager/pom.xml +++ b/opendaylight/adsal/northbound/controllermanager/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../adsal-enunciate-parent controllermanager.northbound 0.1.0-SNAPSHOT @@ -86,17 +86,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/adsal/northbound/flowprogrammer/pom.xml b/opendaylight/adsal/northbound/flowprogrammer/pom.xml index fdc7340a35..3c2aed1310 100644 --- a/opendaylight/adsal/northbound/flowprogrammer/pom.xml +++ b/opendaylight/adsal/northbound/flowprogrammer/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../adsal-enunciate-parent flowprogrammer.northbound @@ -78,17 +78,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/adsal/northbound/hosttracker/pom.xml b/opendaylight/adsal/northbound/hosttracker/pom.xml index 778125af28..4dea525e0b 100644 --- a/opendaylight/adsal/northbound/hosttracker/pom.xml +++ b/opendaylight/adsal/northbound/hosttracker/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../adsal-enunciate-parent hosttracker.northbound 0.5.0-SNAPSHOT @@ -80,17 +80,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/adsal/northbound/java-client/pom.xml b/opendaylight/adsal/northbound/java-client/pom.xml index 5a4c18e88c..df6b86eea5 100644 --- a/opendaylight/adsal/northbound/java-client/pom.xml +++ b/opendaylight/adsal/northbound/java-client/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../../commons/enunciate-parent northbound.client @@ -159,10 +159,6 @@ - - org.codehaus.enunciate - maven-enunciate-plugin - org.codehaus.mojo diff --git a/opendaylight/adsal/northbound/networkconfiguration/bridgedomain/pom.xml b/opendaylight/adsal/northbound/networkconfiguration/bridgedomain/pom.xml index f78ff96608..659bfe9030 100644 --- a/opendaylight/adsal/northbound/networkconfiguration/bridgedomain/pom.xml +++ b/opendaylight/adsal/northbound/networkconfiguration/bridgedomain/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../../commons/opendaylight + ../../../adsal-enunciate-parent networkconfig.bridgedomain.northbound @@ -78,17 +78,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/adsal/northbound/staticrouting/pom.xml b/opendaylight/adsal/northbound/staticrouting/pom.xml index 3a89369b18..be84c61814 100644 --- a/opendaylight/adsal/northbound/staticrouting/pom.xml +++ b/opendaylight/adsal/northbound/staticrouting/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../adsal-enunciate-parent forwarding.staticrouting.northbound @@ -74,17 +74,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/adsal/northbound/statistics/pom.xml b/opendaylight/adsal/northbound/statistics/pom.xml index 0d42ffbb80..9e98a75c44 100644 --- a/opendaylight/adsal/northbound/statistics/pom.xml +++ b/opendaylight/adsal/northbound/statistics/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../adsal-enunciate-parent statistics.northbound @@ -82,17 +82,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/adsal/northbound/subnets/pom.xml b/opendaylight/adsal/northbound/subnets/pom.xml index 1eaa45dddd..7a96bdc1ea 100644 --- a/opendaylight/adsal/northbound/subnets/pom.xml +++ b/opendaylight/adsal/northbound/subnets/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../adsal-enunciate-parent subnets.northbound @@ -39,6 +39,36 @@ + + + + org.codehaus.enunciate + maven-enunciate-plugin + + + org.opendaylight.controller + clustering.services + ${clustering.services.version} + + + org.opendaylight.controller + configuration + ${configuration.version} + + + org.opendaylight.controller + sal + ${sal.version} + + + org.opendaylight.controller + switchmanager + ${switchmanager.api.version} + + + + + org.apache.felix @@ -75,32 +105,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - clustering.services - ${clustering.services.version} - - - org.opendaylight.controller - configuration - ${configuration.version} - - - org.opendaylight.controller - sal - ${sal.version} - - - org.opendaylight.controller - switchmanager - ${switchmanager.api.version} - - - diff --git a/opendaylight/adsal/northbound/switchmanager/pom.xml b/opendaylight/adsal/northbound/switchmanager/pom.xml index b7aaae58d0..c3c380444c 100644 --- a/opendaylight/adsal/northbound/switchmanager/pom.xml +++ b/opendaylight/adsal/northbound/switchmanager/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../adsal-enunciate-parent switchmanager.northbound 0.5.0-SNAPSHOT @@ -75,17 +75,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/adsal/northbound/topology/pom.xml b/opendaylight/adsal/northbound/topology/pom.xml index 60a12700aa..62399c5f04 100644 --- a/opendaylight/adsal/northbound/topology/pom.xml +++ b/opendaylight/adsal/northbound/topology/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../adsal-enunciate-parent topology.northbound @@ -78,17 +78,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/adsal/northbound/usermanager/pom.xml b/opendaylight/adsal/northbound/usermanager/pom.xml index e51ef2faa9..b08995f560 100644 --- a/opendaylight/adsal/northbound/usermanager/pom.xml +++ b/opendaylight/adsal/northbound/usermanager/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../../commons/enunciate-parent usermanager.northbound @@ -66,10 +66,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - diff --git a/opendaylight/adsal/pom.xml b/opendaylight/adsal/pom.xml index 23db4d56a6..682d2a0729 100644 --- a/opendaylight/adsal/pom.xml +++ b/opendaylight/adsal/pom.xml @@ -95,6 +95,7 @@ samples/northbound/loadbalancer dummy-console + adsal-enunciate-parent features/base diff --git a/opendaylight/adsal/samples/northbound/loadbalancer/pom.xml b/opendaylight/adsal/samples/northbound/loadbalancer/pom.xml index e63e992b3f..b5ca85ee7d 100644 --- a/opendaylight/adsal/samples/northbound/loadbalancer/pom.xml +++ b/opendaylight/adsal/samples/northbound/loadbalancer/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + adsal-enunciate-parent 1.5.0-SNAPSHOT - ../../../../commons/opendaylight + ../../../adsal-enunciate-parent samples.loadbalancer.northbound @@ -74,17 +74,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - - - org.opendaylight.controller - sal - ${sal.version} - - - diff --git a/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/__artifactId__-features/pom.xml b/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/__artifactId__-features/pom.xml index 67cc60ab0f..49b43f442e 100644 --- a/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/__artifactId__-features/pom.xml +++ b/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/__artifactId__-features/pom.xml @@ -13,6 +13,7 @@ and is available at http://www.eclipse.org/legal/epl-v10.html INTERNAL org.opendaylight.odlparent features-parent 1.5.0-SNAPSHOT + ${groupId} ${artifactId}-features diff --git a/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/__artifactId__-impl/src/main/config/default-config.xml b/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/__artifactId__-impl/src/main/config/default-config.xml index db4efb83e3..e777fd25bc 100644 --- a/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/__artifactId__-impl/src/main/config/default-config.xml +++ b/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/__artifactId__-impl/src/main/config/default-config.xml @@ -12,6 +12,8 @@ and is available at http://www.eclipse.org/legal/epl-v10.html --> + urn:opendaylight:params:xml:ns:yang:${artifactId}:impl?module=${artifactId}-impl&revision=2014-12-10 + urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding?module=opendaylight-md-sal-binding&revision=2013-10-28 diff --git a/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/pom.xml b/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/pom.xml index 55bf56c241..783b5bf7f2 100644 --- a/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/pom.xml +++ b/opendaylight/archetypes/opendaylight-startup/src/main/resources/archetype-resources/pom.xml @@ -22,4 +22,23 @@ and is available at http://www.eclipse.org/legal/epl-v10.html INTERNAL ${artifactId}-features ${artifactId}-artifacts + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-install-plugin + + true + + + + diff --git a/opendaylight/commons/enunciate-parent/enunciate.xml b/opendaylight/commons/enunciate-parent/enunciate.xml new file mode 100644 index 0000000000..431ea7b316 --- /dev/null +++ b/opendaylight/commons/enunciate-parent/enunciate.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/opendaylight/commons/enunciate-parent/pom.xml b/opendaylight/commons/enunciate-parent/pom.xml new file mode 100644 index 0000000000..2b89137733 --- /dev/null +++ b/opendaylight/commons/enunciate-parent/pom.xml @@ -0,0 +1,42 @@ + + + + + + + org.opendaylight.controller + commons.opendaylight + 1.5.0-SNAPSHOT + ../opendaylight + + + 4.0.0 + enunciate-parent + pom + + + + + non-java8 + + 1.7 + + + + + org.codehaus.enunciate + maven-enunciate-plugin + + + + + + + diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index 32aa9a3efc..cf4aa3140e 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -15,21 +15,14 @@ - 2.3.4 - 1.0.0 + 2.3.9 0.5.0-SNAPSHOT 0.1.0-SNAPSHOT - 1.1.0 0.6.0-SNAPSHOT - - 4.1 - 1.50 - 2.4.0 0.5.0-SNAPSHOT 0.5.0-SNAPSHOT 0.5.0-SNAPSHOT - 2.12 0.6.0-SNAPSHOT 0.5.0-SNAPSHOT 0.5.0-SNAPSHOT @@ -49,17 +42,13 @@ 7.0.53.v201406070630 0.1.0-SNAPSHOT - 1.2.2 0.2.0-SNAPSHOT - 2.4 - 3.1 0.1.0-SNAPSHOT 3.0.1 0.2.0-SNAPSHOT 0.6.0-SNAPSHOT 1.5.0-SNAPSHOT 1.1.0-SNAPSHOT - 2.3.2 0.2.0-SNAPSHOT 0.6.0-SNAPSHOT 1.4 @@ -72,6 +61,7 @@ 04-xsql.xml 01-netconf.xml 03-toaster-sample.xml + 08-mdsal-netconf.xml 10-rest-connector.xml 99-netconf-connector.xml 0.5.0-SNAPSHOT @@ -87,13 +77,8 @@ 30-statistics-manager.xml 2.5.0 3.8.0.I20120518-2145 - - 1.3.1 - 1.28 - 3.0.0 - 0000.0002.0038.0 1.6.0 1.5.0-SNAPSHOT @@ -119,7 +104,6 @@ src/main/yang-gen-config 0.1.0-SNAPSHOT 1.1.4 - 2.0.1 1.1.1 2.0 1.1.0-SNAPSHOT @@ -128,7 +112,6 @@ 0.7 1.8 1.0.0 - 1.0.9 0.5.0-SNAPSHOT 3.0.5 1.9.4 @@ -145,7 +128,6 @@ 1.5.0-SNAPSHOT 2013.08.27.7-SNAPSHOT 0.1.0-SNAPSHOT - 4.0.0 1.1.6 1.1.6 1.0-alpha-2 @@ -209,6 +191,7 @@ 0.7.0-SNAPSHOT 0.12.0 0.9.7 + 3.3.0 @@ -303,11 +286,6 @@ java-concurrent-hash-trie-map ${ctrie.version} - - com.google.code.findbugs - jsr305 - ${jsr305.api.version} - com.google.code.gson gson @@ -318,11 +296,18 @@ guava ${guava.version} + + com.lmax + disruptor + ${lmax.version} + + - com.jcabi - jcabi-maven-slf4j - 0.8 + org.apache.maven + maven-core + 3.1.1 + provided @@ -1254,16 +1239,6 @@ nagasena-rta ${exi.nagasena.version} - - org.osgi - org.osgi.compendium - ${osgi.compendium.version} - - - org.osgi - org.osgi.core - ${osgi.core.version} - org.reflections reflections @@ -1342,6 +1317,15 @@ runtime + + org.opendaylight.controller + features-neutron + features + xml + runtime + ${networkconfig.neutron.version} + + org.openjdk.jmh diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/NeverReconnectStrategyFactoryModule.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/NeverReconnectStrategyFactoryModule.java index a97f65e941..31e7a24fee 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/NeverReconnectStrategyFactoryModule.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/NeverReconnectStrategyFactoryModule.java @@ -17,6 +17,7 @@ import org.opendaylight.protocol.framework.ReconnectStrategyFactory; /** * */ +@Deprecated public final class NeverReconnectStrategyFactoryModule extends org.opendaylight.controller.config.yang.protocol.framework.AbstractNeverReconnectStrategyFactoryModule { diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/NeverReconnectStrategyFactoryModuleFactory.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/NeverReconnectStrategyFactoryModuleFactory.java index bbdc9394cf..7dfe2c9083 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/NeverReconnectStrategyFactoryModuleFactory.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/NeverReconnectStrategyFactoryModuleFactory.java @@ -10,6 +10,7 @@ package org.opendaylight.controller.config.yang.protocol.framework; /** * */ +@Deprecated public class NeverReconnectStrategyFactoryModuleFactory extends org.opendaylight.controller.config.yang.protocol.framework.AbstractNeverReconnectStrategyFactoryModuleFactory { diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/ReconnectImmediatelyStrategyFactoryModule.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/ReconnectImmediatelyStrategyFactoryModule.java index 12b771df80..af86a249df 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/ReconnectImmediatelyStrategyFactoryModule.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/ReconnectImmediatelyStrategyFactoryModule.java @@ -17,6 +17,7 @@ import org.opendaylight.protocol.framework.ReconnectStrategyFactory; /** * */ +@Deprecated public final class ReconnectImmediatelyStrategyFactoryModule extends org.opendaylight.controller.config.yang.protocol.framework.AbstractReconnectImmediatelyStrategyFactoryModule { diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/ReconnectImmediatelyStrategyFactoryModuleFactory.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/ReconnectImmediatelyStrategyFactoryModuleFactory.java index 6e72458be8..7aa31f30cd 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/ReconnectImmediatelyStrategyFactoryModuleFactory.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/ReconnectImmediatelyStrategyFactoryModuleFactory.java @@ -10,6 +10,7 @@ package org.opendaylight.controller.config.yang.protocol.framework; /** * */ +@Deprecated public class ReconnectImmediatelyStrategyFactoryModuleFactory extends org.opendaylight.controller.config.yang.protocol.framework.AbstractReconnectImmediatelyStrategyFactoryModuleFactory { diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/TimedReconnectStrategyFactoryModule.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/TimedReconnectStrategyFactoryModule.java index 0b4a7baf6f..ab0602a965 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/TimedReconnectStrategyFactoryModule.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/TimedReconnectStrategyFactoryModule.java @@ -19,6 +19,7 @@ import com.google.common.base.Preconditions; /** * */ +@Deprecated public final class TimedReconnectStrategyFactoryModule extends org.opendaylight.controller.config.yang.protocol.framework.AbstractTimedReconnectStrategyFactoryModule { diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/TimedReconnectStrategyFactoryModuleFactory.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/TimedReconnectStrategyFactoryModuleFactory.java index 7f92e3a408..eac0c66fd8 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/TimedReconnectStrategyFactoryModuleFactory.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/controller/config/yang/protocol/framework/TimedReconnectStrategyFactoryModuleFactory.java @@ -10,6 +10,7 @@ package org.opendaylight.controller.config.yang.protocol.framework; /** * */ +@Deprecated public class TimedReconnectStrategyFactoryModuleFactory extends org.opendaylight.controller.config.yang.protocol.framework.AbstractTimedReconnectStrategyFactoryModuleFactory { diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java index a05d02cd09..7b8816bd1b 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java @@ -39,6 +39,7 @@ import io.netty.util.concurrent.Promise; * Dispatcher class for creating servers and clients. The idea is to first create servers and clients and the run the * start method that will handle sockets in different thread. */ +@Deprecated public abstract class AbstractDispatcher, L extends SessionListener> implements Closeable { @@ -238,7 +239,7 @@ public abstract class AbstractDispatcher, L extends * @param connectStrategyFactory Factory for creating reconnection strategy for every reconnect attempt * * @return Future representing the reconnection task. It will report completion based on reestablishStrategy, e.g. - * success if it indicates no further attempts should be made and failure if it reports an error + * success is never reported, only failure when it runs out of reconnection attempts. */ protected Future createReconnectingClient(final InetSocketAddress address, final ReconnectStrategyFactory connectStrategyFactory, final PipelineInitializer initializer) { diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractProtocolSession.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractProtocolSession.java index af196a941a..44afc4e7a1 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractProtocolSession.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractProtocolSession.java @@ -13,6 +13,7 @@ import io.netty.channel.SimpleChannelInboundHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Deprecated public abstract class AbstractProtocolSession extends SimpleChannelInboundHandler implements ProtocolSession { private static final Logger LOG = LoggerFactory.getLogger(AbstractProtocolSession.class); diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractSessionNegotiator.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractSessionNegotiator.java index 2ecd267b9f..8a19828c57 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractSessionNegotiator.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractSessionNegotiator.java @@ -27,6 +27,7 @@ import com.google.common.base.Preconditions; * @param Protocol message type * @param Protocol session type, has to extend ProtocolSession */ +@Deprecated public abstract class AbstractSessionNegotiator> extends ChannelInboundHandlerAdapter implements SessionNegotiator { private final Logger LOG = LoggerFactory.getLogger(AbstractSessionNegotiator.class); private final Promise promise; diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/NeverReconnectStrategy.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/NeverReconnectStrategy.java index c480294417..926183f973 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/NeverReconnectStrategy.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/NeverReconnectStrategy.java @@ -18,6 +18,7 @@ import com.google.common.base.Preconditions; * Utility ReconnectStrategy singleton, which will cause the reconnect process * to always fail. */ +@Deprecated @ThreadSafe public final class NeverReconnectStrategy implements ReconnectStrategy { private final EventExecutor executor; diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSession.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSession.java index 6e79d6765d..b99844112f 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSession.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSession.java @@ -17,6 +17,7 @@ import java.io.Closeable; * * This interface should be implemented by a final class representing a protocol specific session. */ +@Deprecated public interface ProtocolSession extends Closeable { @Override void close(); diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSessionPromise.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSessionPromise.java index a38db61ead..b64c4828a4 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSessionPromise.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSessionPromise.java @@ -23,6 +23,7 @@ import javax.annotation.concurrent.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Deprecated @ThreadSafe final class ProtocolSessionPromise> extends DefaultPromise { private static final Logger LOG = LoggerFactory.getLogger(ProtocolSessionPromise.class); diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectImmediatelyStrategy.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectImmediatelyStrategy.java index a567af1f76..56cdcfb0c3 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectImmediatelyStrategy.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectImmediatelyStrategy.java @@ -21,6 +21,7 @@ import com.google.common.base.Preconditions; * Utility ReconnectStrategy singleton, which will cause the reconnect process * to immediately schedule a reconnection attempt. */ +@Deprecated @ThreadSafe public final class ReconnectImmediatelyStrategy implements ReconnectStrategy { private static final Logger LOG = LoggerFactory.getLogger(ReconnectImmediatelyStrategy.class); diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java index aaec95a74b..e57976449b 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java @@ -15,11 +15,13 @@ import io.netty.channel.socket.SocketChannel; import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; import java.net.InetSocketAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Deprecated final class ReconnectPromise, L extends SessionListener> extends DefaultPromise { private static final Logger LOG = LoggerFactory.getLogger(ReconnectPromise.class); @@ -55,6 +57,15 @@ final class ReconnectPromise, L extends SessionList channel.pipeline().addLast(new ClosedChannelHandler(ReconnectPromise.this)); } }); + + pending.addListener(new GenericFutureListener>() { + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + ReconnectPromise.this.setFailure(future.cause()); + } + } + }); } /** diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectStrategy.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectStrategy.java index 24ff84b6ab..a0a9150799 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectStrategy.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectStrategy.java @@ -23,6 +23,7 @@ import io.netty.util.concurrent.Future; * not attempt any more connection attempts and should abort the reconnection * process. */ +@Deprecated public interface ReconnectStrategy { /** * Query the strategy for the connect timeout. diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectStrategyFactory.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectStrategyFactory.java index 3c61044bee..a71fa677c8 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectStrategyFactory.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectStrategyFactory.java @@ -12,6 +12,7 @@ package org.opendaylight.protocol.framework; * primarily useful for allowing injection of a specific type of strategy for * on-demand use, pretty much like you would use a ThreadFactory. */ +@Deprecated public interface ReconnectStrategyFactory { /** * Create a new ReconnectStrategy. diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListener.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListener.java index a756a0da7e..dfe0208c54 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListener.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListener.java @@ -14,6 +14,7 @@ import java.util.EventListener; * implemented by a protocol specific abstract class, that is extended by * a final class that implements the methods. */ +@Deprecated public interface SessionListener, T extends TerminationReason> extends EventListener { /** * Fired when the session was established successfully. diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListenerFactory.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListenerFactory.java index 11871286cf..99087a5434 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListenerFactory.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListenerFactory.java @@ -13,6 +13,7 @@ package org.opendaylight.protocol.framework; * implemented by a protocol specific abstract class, that is extended by * a final class that implements the methods. */ +@Deprecated public interface SessionListenerFactory> { /** * Returns one session listener diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionNegotiator.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionNegotiator.java index 3de64b07ff..c2abf50716 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionNegotiator.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionNegotiator.java @@ -17,6 +17,7 @@ import io.netty.channel.ChannelInboundHandler; * * @param Protocol session type. */ +@Deprecated public interface SessionNegotiator> extends ChannelInboundHandler { } diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionNegotiatorFactory.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionNegotiatorFactory.java index 90844ca712..66293f368f 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionNegotiatorFactory.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionNegotiatorFactory.java @@ -15,6 +15,7 @@ import io.netty.util.concurrent.Promise; * * @param session type */ +@Deprecated public interface SessionNegotiatorFactory, L extends SessionListener> { /** * Create a new negotiator attached to a channel, which will notify diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TerminationReason.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TerminationReason.java index 1a6179dc35..19d11125e5 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TerminationReason.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TerminationReason.java @@ -10,6 +10,7 @@ package org.opendaylight.protocol.framework; /** * Marker interface for grouping session termination cause. */ +@Deprecated public interface TerminationReason { /** diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TimedReconnectStrategy.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TimedReconnectStrategy.java index 8bb326821d..ecb9e65a52 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TimedReconnectStrategy.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/TimedReconnectStrategy.java @@ -42,6 +42,7 @@ import com.google.common.base.Preconditions; * * Both these caps can be combined, with the strategy giving up as soon as the first one is reached. */ +@Deprecated @ThreadSafe public final class TimedReconnectStrategy implements ReconnectStrategy { private static final Logger LOG = LoggerFactory.getLogger(TimedReconnectStrategy.class); diff --git a/opendaylight/config/config-api/pom.xml b/opendaylight/config/config-api/pom.xml index eff635c17e..d76bd50a3d 100644 --- a/opendaylight/config/config-api/pom.xml +++ b/opendaylight/config/config-api/pom.xml @@ -28,6 +28,10 @@ org.osgi org.osgi.core + + + compile org.opendaylight.yangtools diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsHelper.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsHelper.java index efb357466d..fba9844dbf 100644 --- a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsHelper.java +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/dynamicmbean/AnnotationsHelper.java @@ -7,6 +7,8 @@ */ package org.opendaylight.controller.config.manager.impl.dynamicmbean; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; @@ -29,9 +31,9 @@ public class AnnotationsHelper { * @return list of found annotations */ static List findMethodAnnotationInSuperClassesAndIfcs( - final Method setter, Class annotationType, - Set> inspectedInterfaces) { - List result = new ArrayList(); + final Method setter, final Class annotationType, + final Set> inspectedInterfaces) { + Builder result = ImmutableSet.builder(); Class inspectedClass = setter.getDeclaringClass(); do { try { @@ -46,7 +48,8 @@ public class AnnotationsHelper { } catch (NoSuchMethodException e) { inspectedClass = Object.class; // no need to go further } - } while (inspectedClass.equals(Object.class) == false); + } while (!inspectedClass.equals(Object.class)); + // inspect interfaces for (Class ifc : inspectedInterfaces) { if (ifc.isInterface() == false) { @@ -63,7 +66,7 @@ public class AnnotationsHelper { } } - return result; + return new ArrayList<>(result.build()); } /** @@ -74,7 +77,7 @@ public class AnnotationsHelper { * @return list of found annotations */ static List findClassAnnotationInSuperClassesAndIfcs( - Class clazz, Class annotationType, Set> interfaces) { + final Class clazz, final Class annotationType, final Set> interfaces) { List result = new ArrayList(); Class declaringClass = clazz; do { @@ -101,7 +104,7 @@ public class AnnotationsHelper { * @return empty string if no annotation is found, or list of descriptions * separated by newline */ - static String aggregateDescriptions(List descriptions) { + static String aggregateDescriptions(final List descriptions) { StringBuilder builder = new StringBuilder(); for (Description d : descriptions) { if (builder.length() != 0) { diff --git a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelperTest.java b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelperTest.java index 34039ce8d0..5656163cbe 100644 --- a/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelperTest.java +++ b/opendaylight/config/config-manager/src/test/java/org/opendaylight/controller/config/manager/impl/util/InterfacesHelperTest.java @@ -8,7 +8,6 @@ package org.opendaylight.controller.config.manager.impl.util; import static org.junit.Assert.assertEquals; - import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.Collections; @@ -25,37 +24,37 @@ import org.opendaylight.yangtools.concepts.Identifiable; public class InterfacesHelperTest { - interface SuperA { + public interface SuperA { } - interface SuperBMXBean { + public interface SuperBMXBean { } - interface SuperC extends SuperA, SuperBMXBean { + public interface SuperC extends SuperA, SuperBMXBean { } - class SuperClass implements SuperC { + public class SuperClass implements SuperC { } @MXBean - interface SubA { + public interface SubA { } @ServiceInterfaceAnnotation(value = "a", osgiRegistrationType = SuperA.class, namespace = "n", revision = "r", localName = "l") - interface Service extends AbstractServiceInterface{} + public interface Service extends AbstractServiceInterface{} @ServiceInterfaceAnnotation(value = "b", osgiRegistrationType = SuperC.class, namespace = "n", revision = "r", localName = "l") - interface SubService extends Service{} + public interface SubService extends Service{} - abstract class SubClass extends SuperClass implements SubA, Module { + public abstract class SubClass extends SuperClass implements SubA, Module { } - abstract class SubClassWithService implements SubService, Module { + public abstract class SubClassWithService implements SubService, Module { } diff --git a/opendaylight/config/config-parent/pom.xml b/opendaylight/config/config-parent/pom.xml index af39b63447..e6e2bb8478 100644 --- a/opendaylight/config/config-parent/pom.xml +++ b/opendaylight/config/config-parent/pom.xml @@ -123,6 +123,25 @@ and is available at http://www.eclipse.org/legal/epl-v10.html + + org.codehaus.mojo + build-helper-maven-plugin + + + add-yang-sources + generate-sources + + add-source + + + + ${jmxGeneratorPath} + ${salGeneratorPath} + + + + + diff --git a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPushingRunnable.java b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPushingRunnable.java index 5c257effba..a32a0ac28f 100644 --- a/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPushingRunnable.java +++ b/opendaylight/config/config-persister-feature-adapter/src/main/java/org/opendaylight/controller/configpusherfeature/internal/ConfigPushingRunnable.java @@ -33,7 +33,7 @@ public class ConfigPushingRunnable implements Runnable { @Override public void run() { List toInstall = new ArrayList(); - FeatureEvent event; + FeatureEvent event = null; boolean interuppted = false; while(true) { try { @@ -54,7 +54,7 @@ public class ConfigPushingRunnable implements Runnable { LOG.error("ConfigPushingRunnable - interupted"); interuppted = true; } catch (Exception e) { - LOG.error("Exception while processing features ", e); + LOG.error("Exception while processing features {} event {}", toInstall, event, e); } } } diff --git a/opendaylight/config/config-plugin-parent/pom.xml b/opendaylight/config/config-plugin-parent/pom.xml index 67370e1e2f..081df0c52d 100644 --- a/opendaylight/config/config-plugin-parent/pom.xml +++ b/opendaylight/config/config-plugin-parent/pom.xml @@ -11,9 +11,6 @@ config-plugin-parent pom ${project.artifactId} - - 3.0.4 - diff --git a/opendaylight/config/logback-config-loader/pom.xml b/opendaylight/config/logback-config-loader/pom.xml index 0f379fbe21..94f7f8fc49 100644 --- a/opendaylight/config/logback-config-loader/pom.xml +++ b/opendaylight/config/logback-config-loader/pom.xml @@ -11,9 +11,6 @@ logback-config-loader bundle ${project.artifactId} - - 3.0.4 - diff --git a/opendaylight/config/logback-config/pom.xml b/opendaylight/config/logback-config/pom.xml index d918fd7ab7..d4537387aa 100644 --- a/opendaylight/config/logback-config/pom.xml +++ b/opendaylight/config/logback-config/pom.xml @@ -11,9 +11,6 @@ logback-config bundle ${project.artifactId} - - 3.0.4 - diff --git a/opendaylight/config/netty-config-api/pom.xml b/opendaylight/config/netty-config-api/pom.xml index a5c0831fb8..2d8145723d 100644 --- a/opendaylight/config/netty-config-api/pom.xml +++ b/opendaylight/config/netty-config-api/pom.xml @@ -11,9 +11,6 @@ netty-config-api bundle ${project.artifactId} - - 3.0.4 - diff --git a/opendaylight/config/netty-event-executor-config/pom.xml b/opendaylight/config/netty-event-executor-config/pom.xml index 6188aed898..31940b91fe 100644 --- a/opendaylight/config/netty-event-executor-config/pom.xml +++ b/opendaylight/config/netty-event-executor-config/pom.xml @@ -12,9 +12,6 @@ bundle ${project.artifactId} Configuration Wrapper around netty's event executor - - 3.0.4 - diff --git a/opendaylight/config/netty-threadgroup-config/pom.xml b/opendaylight/config/netty-threadgroup-config/pom.xml index 2f3d26dd2f..0f645015e1 100644 --- a/opendaylight/config/netty-threadgroup-config/pom.xml +++ b/opendaylight/config/netty-threadgroup-config/pom.xml @@ -14,9 +14,6 @@ bundle ${project.artifactId} Configuration Wrapper around netty's event group - - 3.0.4 - diff --git a/opendaylight/config/netty-timer-config/pom.xml b/opendaylight/config/netty-timer-config/pom.xml index 75b4709da2..181c1d0151 100644 --- a/opendaylight/config/netty-timer-config/pom.xml +++ b/opendaylight/config/netty-timer-config/pom.xml @@ -12,9 +12,6 @@ bundle ${project.artifactId} Configuration Wrapper around netty's timer - - 3.0.4 - diff --git a/opendaylight/config/pom.xml b/opendaylight/config/pom.xml index 4c4c5b3378..6d14ad3957 100644 --- a/opendaylight/config/pom.xml +++ b/opendaylight/config/pom.xml @@ -13,9 +13,7 @@ 0.3.0-SNAPSHOT pom ${project.artifactId} - - 3.0.4 - + config-api config-manager diff --git a/opendaylight/config/threadpool-config-api/pom.xml b/opendaylight/config/threadpool-config-api/pom.xml index 5f0c941a19..9dc7bf5976 100644 --- a/opendaylight/config/threadpool-config-api/pom.xml +++ b/opendaylight/config/threadpool-config-api/pom.xml @@ -11,9 +11,6 @@ threadpool-config-api bundle ${project.artifactId} - - 3.0.4 - diff --git a/opendaylight/config/threadpool-config-impl/pom.xml b/opendaylight/config/threadpool-config-impl/pom.xml index 2787b30df4..b875f5f3e4 100644 --- a/opendaylight/config/threadpool-config-impl/pom.xml +++ b/opendaylight/config/threadpool-config-impl/pom.xml @@ -11,9 +11,6 @@ threadpool-config-impl bundle ${project.artifactId} - - 3.0.4 - diff --git a/opendaylight/config/yang-jmx-generator-plugin/pom.xml b/opendaylight/config/yang-jmx-generator-plugin/pom.xml index 6c8a591bb8..ae190848c9 100644 --- a/opendaylight/config/yang-jmx-generator-plugin/pom.xml +++ b/opendaylight/config/yang-jmx-generator-plugin/pom.xml @@ -21,11 +21,6 @@ guava - - com.jcabi - jcabi-maven-slf4j - - commons-io commons-io @@ -36,17 +31,6 @@ commons-lang3 - - org.codehaus.gmaven.runtime - gmaven-runtime-2.0 - - - org.sonatype.gossip - gossip - - - - org.opendaylight.controller yang-jmx-generator @@ -66,6 +50,10 @@ org.opendaylight.yangtools yang-maven-plugin-spi + + org.apache.maven + maven-core + org.slf4j diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java index 9ad8d2826f..1f1776f0a5 100644 --- a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java @@ -23,7 +23,6 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; -import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.opendaylight.controller.config.spi.ModuleFactory; import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; @@ -35,42 +34,54 @@ import org.opendaylight.yangtools.yang.common.QName; 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.yang2sources.spi.CodeGenerator; +import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator; +import org.opendaylight.yangtools.yang2sources.spi.MavenProjectAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.impl.StaticLoggerBinder; /** * This class interfaces with yang-maven-plugin. Gets parsed yang modules in * {@link SchemaContext}, and parameters form the plugin configuration, and * writes service interfaces and/or modules. */ -public class JMXGenerator implements CodeGenerator { +public class JMXGenerator implements BasicCodeGenerator, MavenProjectAware { + private static final class NamespaceMapping { + private final String namespace, packageName; + public NamespaceMapping(final String namespace, final String packagename) { + this.namespace = namespace; + this.packageName = packagename; + } + } + + @VisibleForTesting static final String NAMESPACE_TO_PACKAGE_DIVIDER = "=="; + @VisibleForTesting static final String NAMESPACE_TO_PACKAGE_PREFIX = "namespaceToPackage"; + @VisibleForTesting static final String MODULE_FACTORY_FILE_BOOLEAN = "moduleFactoryFile"; + private static final Logger LOG = LoggerFactory.getLogger(JMXGenerator.class); + private static final Pattern NAMESPACE_MAPPING_PATTERN = Pattern.compile("(.+)" + NAMESPACE_TO_PACKAGE_DIVIDER + "(.+)"); + private PackageTranslator packageTranslator; private final CodeWriter codeWriter; - private static final Logger LOG = LoggerFactory - .getLogger(JMXGenerator.class); private Map namespaceToPackageMapping; private File resourceBaseDir; private File projectBaseDir; private boolean generateModuleFactoryFile = true; public JMXGenerator() { - this.codeWriter = new CodeWriter(); + this(new CodeWriter()); } - public JMXGenerator(CodeWriter codeWriter) { + public JMXGenerator(final CodeWriter codeWriter) { this.codeWriter = codeWriter; } @Override - public Collection generateSources(SchemaContext context, - File outputBaseDir, Set yangModulesInCurrentMavenModule) { + public Collection generateSources(final SchemaContext context, + final File outputBaseDir, final Set yangModulesInCurrentMavenModule) { Preconditions.checkArgument(context != null, "Null context received"); Preconditions.checkArgument(outputBaseDir != null, @@ -173,7 +184,8 @@ public class JMXGenerator implements CodeGenerator { return generatedFiles.getFiles(); } - static File concatFolders(File projectBaseDir, String... folderNames) { + @VisibleForTesting + static File concatFolders(final File projectBaseDir, final String... folderNames) { StringBuilder b = new StringBuilder(); for (String folder : folderNames) { b.append(folder); @@ -183,18 +195,14 @@ public class JMXGenerator implements CodeGenerator { } @Override - public void setAdditionalConfig(Map additionalCfg) { - if (LOG != null) { - LOG.debug(getClass().getCanonicalName(), - ": Additional configuration received: ", - additionalCfg.toString()); - } + public void setAdditionalConfig(final Map additionalCfg) { + LOG.debug("{}: Additional configuration received: {}", getClass().getCanonicalName(), additionalCfg); this.namespaceToPackageMapping = extractNamespaceMapping(additionalCfg); this.generateModuleFactoryFile = extractModuleFactoryBoolean(additionalCfg); } private boolean extractModuleFactoryBoolean( - Map additionalCfg) { + final Map additionalCfg) { String bool = additionalCfg.get(MODULE_FACTORY_FILE_BOOLEAN); if (bool == null) { return true; @@ -205,13 +213,8 @@ public class JMXGenerator implements CodeGenerator { return true; } - @Override - public void setLog(Log log) { - StaticLoggerBinder.getSingleton().setMavenLog(log); - } - private static Map extractNamespaceMapping( - Map additionalCfg) { + final Map additionalCfg) { Map namespaceToPackage = Maps.newHashMap(); for (String key : additionalCfg.keySet()) { if (key.startsWith(NAMESPACE_TO_PACKAGE_PREFIX)) { @@ -224,46 +227,30 @@ public class JMXGenerator implements CodeGenerator { return namespaceToPackage; } - static Pattern namespaceMappingPattern = Pattern.compile("(.+)" - + NAMESPACE_TO_PACKAGE_DIVIDER + "(.+)"); - - private static NamespaceMapping extractNamespaceMapping(String mapping) { - Matcher matcher = namespaceMappingPattern.matcher(mapping); - Preconditions - .checkArgument(matcher.matches(), String.format("Namespace to package mapping:%s is in invalid " + - "format, requested format is: %s", mapping, namespaceMappingPattern)); + private static NamespaceMapping extractNamespaceMapping(final String mapping) { + Matcher matcher = NAMESPACE_MAPPING_PATTERN.matcher(mapping); + Preconditions.checkArgument(matcher.matches(), + "Namespace to package mapping:%s is in invalid format, requested format is: %s", + mapping, NAMESPACE_MAPPING_PATTERN); return new NamespaceMapping(matcher.group(1), matcher.group(2)); } - private static class NamespaceMapping { - public NamespaceMapping(String namespace, String packagename) { - this.namespace = namespace; - this.packageName = packagename; - } - - private final String namespace, packageName; - } - @Override - public void setResourceBaseDir(File resourceDir) { + public void setResourceBaseDir(final File resourceDir) { this.resourceBaseDir = resourceDir; } @Override - public void setMavenProject(MavenProject project) { + public void setMavenProject(final MavenProject project) { this.projectBaseDir = project.getBasedir(); - - if (LOG != null) { - LOG.debug(getClass().getCanonicalName(), " project base dir: ", - projectBaseDir); - } + LOG.debug("{}: project base dir: {}", getClass().getCanonicalName(), projectBaseDir); } @VisibleForTesting static class GeneratedFilesTracker { private final Set files = Sets.newHashSet(); - void addFile(File file) { + void addFile(final File file) { if (files.contains(file)) { List undeletedFiles = Lists.newArrayList(); for (File presentFile : files) { @@ -283,7 +270,7 @@ public class JMXGenerator implements CodeGenerator { files.add(file); } - void addFile(Collection files) { + void addFile(final Collection files) { for (File file : files) { addFile(file); } diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TemplateFactory.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TemplateFactory.java index 19e875f9b1..00454d8acf 100644 --- a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TemplateFactory.java +++ b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ftl/TemplateFactory.java @@ -48,9 +48,9 @@ import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.Meth import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.MethodDefinition; import org.opendaylight.controller.config.yangjmxgenerator.plugin.ftl.model.ModuleField; import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper; -import org.opendaylight.yangtools.binding.generator.util.BindingGeneratorUtil; import org.opendaylight.yangtools.sal.binding.model.api.ParameterizedType; import org.opendaylight.yangtools.sal.binding.model.api.Type; +import org.opendaylight.yangtools.yang.binding.BindingMapping; public class TemplateFactory { @@ -59,7 +59,7 @@ public class TemplateFactory { * bean as value that should be persisted from this instance. */ public static Map getTOAndMXInterfaceFtlFiles( - RuntimeBeanEntry entry) { + final RuntimeBeanEntry entry) { Map result = new HashMap<>(); { // create GeneralInterfaceFtlFile for runtime MXBean. Attributes will // be transformed to getter methods @@ -109,7 +109,7 @@ public class TemplateFactory { } // FIXME: put into Type.toString - static String serializeType(Type type, boolean addWildcards) { + static String serializeType(final Type type, final boolean addWildcards) { if (type instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) type; StringBuilder sb = new StringBuilder(); @@ -131,11 +131,11 @@ public class TemplateFactory { } } - static String serializeType(Type type) { + static String serializeType(final Type type) { return serializeType(type, false); } - private static String getReturnType(AttributeIfc attributeIfc) { + private static String getReturnType(final AttributeIfc attributeIfc) { String returnType; if (attributeIfc instanceof TypedAttribute) { Type type = ((TypedAttribute) attributeIfc).getType(); @@ -151,7 +151,7 @@ public class TemplateFactory { } public static GeneralInterfaceTemplate serviceInterfaceFromSie( - ServiceInterfaceEntry sie) { + final ServiceInterfaceEntry sie) { List extendedInterfaces = Lists .newArrayList(AbstractServiceInterface.class.getCanonicalName()); @@ -177,7 +177,7 @@ public class TemplateFactory { } public static AbstractFactoryTemplate abstractFactoryTemplateFromMbe( - ModuleMXBeanEntry mbe) { + final ModuleMXBeanEntry mbe) { AbstractFactoryAttributesProcessor attrProcessor = new AbstractFactoryAttributesProcessor(); attrProcessor.processAttributes(mbe.getAttributes(), mbe.getPackageName()); @@ -191,7 +191,7 @@ public class TemplateFactory { } public static AbstractModuleTemplate abstractModuleTemplateFromMbe( - ModuleMXBeanEntry mbe) { + final ModuleMXBeanEntry mbe) { AbstractModuleAttributesProcessor attrProcessor = new AbstractModuleAttributesProcessor(mbe.getAttributes()); List moduleFields = attrProcessor.getModuleFields(); @@ -234,7 +234,7 @@ public class TemplateFactory { } public static StubFactoryTemplate stubFactoryTemplateFromMbe( - ModuleMXBeanEntry mbe) { + final ModuleMXBeanEntry mbe) { return new StubFactoryTemplate(getHeaderFromEntry(mbe), mbe.getPackageName(), mbe.getStubFactoryName(), mbe.getFullyQualifiedName(mbe.getAbstractFactoryName()) @@ -242,7 +242,7 @@ public class TemplateFactory { } public static GeneralInterfaceTemplate mXBeanInterfaceTemplateFromMbe( - ModuleMXBeanEntry mbe) { + final ModuleMXBeanEntry mbe) { MXBeanInterfaceAttributesProcessor attrProcessor = new MXBeanInterfaceAttributesProcessor(); attrProcessor.processAttributes(mbe.getAttributes()); GeneralInterfaceTemplate ifcTemplate = new GeneralInterfaceTemplate( @@ -254,7 +254,7 @@ public class TemplateFactory { } public static Map tOsFromMbe( - ModuleMXBeanEntry mbe) { + final ModuleMXBeanEntry mbe) { Map retVal = Maps.newHashMap(); TOAttributesProcessor processor = new TOAttributesProcessor(); processor.processAttributes(mbe.getAttributes()); @@ -275,7 +275,7 @@ public class TemplateFactory { } public static Map tOsFromRbe( - RuntimeBeanEntry rbe) { + final RuntimeBeanEntry rbe) { Map retVal = Maps.newHashMap(); TOAttributesProcessor processor = new TOAttributesProcessor(); Map yangPropertiesToTypesMap = Maps.newHashMap(rbe.getYangPropertiesToTypesMap()); @@ -316,7 +316,7 @@ public class TemplateFactory { return retVal; } - private static Header getHeaderFromEntry(AbstractEntry mbe) { + private static Header getHeaderFromEntry(final AbstractEntry mbe) { return new Header(mbe.getYangModuleName(), mbe.getYangModuleLocalname()); } @@ -326,7 +326,7 @@ public class TemplateFactory { private final List tos = Lists.newArrayList(); - void processAttributes(Map attributes) { + void processAttributes(final Map attributes) { for (Entry attrEntry : attributes.entrySet()) { AttributeIfc attributeIfc = attrEntry.getValue(); if (attributeIfc instanceof TOAttribute) { @@ -342,7 +342,7 @@ public class TemplateFactory { } } - private void createTOInternal(TOAttribute toAttribute) { + private void createTOInternal(final TOAttribute toAttribute) { Map attrs = toAttribute.getCapitalizedPropertiesToTypesMap(); // recursive processing of TO's attributes @@ -360,12 +360,12 @@ public class TemplateFactory { private List fields; private List methods; - public TOInternal(Type type, Map attrs) { + public TOInternal(final Type type, final Map attrs) { this(type.getFullyQualifiedName(), type.getName(), attrs, type.getPackageName()); } - public TOInternal(String fullyQualifiedName, String name, - Map attrs, String packageName) { + public TOInternal(final String fullyQualifiedName, final String name, + final Map attrs, final String packageName) { this.fullyQualifiedName = fullyQualifiedName; this.name = name; processAttrs(attrs, packageName); @@ -374,7 +374,7 @@ public class TemplateFactory { private final static String dependencyResolverVarName = "dependencyResolver"; private final static String dependencyResolverInjectMethodName = "injectDependencyResolver"; - private void processAttrs(Map attrs, String packageName) { + private void processAttrs(final Map attrs, final String packageName) { fields = Lists.newArrayList(); methods = Lists.newArrayList(); @@ -386,8 +386,7 @@ public class TemplateFactory { for (Entry attrEntry : attrs.entrySet()) { String innerName = attrEntry.getKey(); - String varName = BindingGeneratorUtil - .parseToValidParamName(attrEntry.getKey()); + String varName = BindingMapping.getPropertyName(attrEntry.getKey()); String fullyQualifiedName, nullableDefault = null; if (attrEntry.getValue() instanceof TypedAttribute) { @@ -449,7 +448,7 @@ public class TemplateFactory { private static class MXBeanInterfaceAttributesProcessor { private final List methods = Lists.newArrayList(); - void processAttributes(Map attributes) { + void processAttributes(final Map attributes) { for (Entry attrEntry : attributes.entrySet()) { String returnType; AttributeIfc attributeIfc = attrEntry.getValue(); @@ -473,8 +472,7 @@ public class TemplateFactory { MethodDeclaration getter = new MethodDeclaration(returnType, getterName, Collections. emptyList()); - String varName = BindingGeneratorUtil - .parseToValidParamName(attrEntry.getKey()); + String varName = BindingMapping.getPropertyName(attrEntry.getKey()); String setterName = "set" + attributeIfc.getUpperCaseCammelCase(); MethodDeclaration setter = new MethodDeclaration("void", @@ -519,8 +517,8 @@ public class TemplateFactory { private final List fields = Lists.newArrayList(); - void processAttributes(Map attributes, - String packageName) { + void processAttributes(final Map attributes, + final String packageName) { for (Entry attrEntry : attributes.entrySet()) { String type; String nullableDefaultWrapped = null; @@ -550,7 +548,7 @@ public class TemplateFactory { private final List moduleFields; private final List methods; - private Holder(List moduleFields, List methods) { + private Holder(final List moduleFields, final List methods) { this.moduleFields = Collections.unmodifiableList(moduleFields); this.methods = Collections.unmodifiableList(methods); } @@ -559,11 +557,11 @@ public class TemplateFactory { private final Holder holder; - private AbstractModuleAttributesProcessor(Map attributes) { + private AbstractModuleAttributesProcessor(final Map attributes) { this.holder = processAttributes(attributes); } - private static Holder processAttributes(Map attributes) { + private static Holder processAttributes(final Map attributes) { List moduleFields = new ArrayList<>(); List methods = new ArrayList<>(); for (Entry attrEntry : attributes.entrySet()) { @@ -607,8 +605,7 @@ public class TemplateFactory { } } - String varName = BindingGeneratorUtil - .parseToValidParamName(attrEntry.getKey()); + String varName = BindingMapping.getPropertyName(attrEntry.getKey()); ModuleField field; if (isIdentity) { @@ -689,7 +686,7 @@ public class TemplateFactory { } - private static boolean needsDepResolver(AttributeIfc value) { + private static boolean needsDepResolver(final AttributeIfc value) { if(value instanceof TOAttribute) { return true; } @@ -701,7 +698,7 @@ public class TemplateFactory { return false; } - private static String getInnerTypeFromIdentity(Type type) { + private static String getInnerTypeFromIdentity(final Type type) { Preconditions.checkArgument(type instanceof ParameterizedType); Type[] args = ((ParameterizedType) type).getActualTypeArguments(); Preconditions.checkArgument(args.length ==1); diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorTest.java index a6cfc58c34..3dae004161 100644 --- a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorTest.java +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGeneratorTest.java @@ -16,11 +16,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; - import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; @@ -42,7 +39,6 @@ import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.io.FileUtils; -import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.compiler.IProblem; @@ -124,13 +120,6 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { File targetDir = new File(generatorOutputPath, "target"); generatedResourcesDir = new File(targetDir, "generated-resources"); jmxGenerator.setResourceBaseDir(generatedResourcesDir); - Log mockedLog = mock(Log.class); - doReturn(false).when(mockedLog).isDebugEnabled(); - doNothing().when(mockedLog).debug(any(CharSequence.class)); - doNothing().when(mockedLog).info(any(CharSequence.class)); - doNothing().when(mockedLog).error(any(CharSequence.class), - any(Throwable.class)); - jmxGenerator.setLog(mockedLog); MavenProject project = mock(MavenProject.class); doReturn(generatorOutputPath).when(project).getBasedir(); jmxGenerator.setMavenProject(project); @@ -158,18 +147,19 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { verifyModuleFactoryFile(false); } - private void verifyModuleFactoryFile(boolean shouldBePresent) { + private void verifyModuleFactoryFile(final boolean shouldBePresent) { File factoryFile = new File(generatedResourcesDir, "META-INF" + File.separator + "services" + File.separator + ModuleFactory.class.getName()); - if (!shouldBePresent) + if (!shouldBePresent) { assertFalse("Factory file should not be generated", factoryFile.exists()); - else + } else { assertTrue("Factory file should be generated", factoryFile.exists()); + } } - public static List toFileNames(Collection files) { + public static List toFileNames(final Collection files) { List result = new ArrayList<>(); for (File f : files) { result.add(f.getName()); @@ -279,7 +269,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { new Predicate() { @Override - public boolean apply(File input) { + public boolean apply(final File input) { return input.getName().endsWith("xml"); } }); @@ -288,7 +278,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { new Predicate() { @Override - public boolean apply(File input) { + public boolean apply(final File input) { return input.getName().endsWith("java"); } }); @@ -303,16 +293,21 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { String name = file.getName(); MbeASTVisitor visitor = new MbeASTVisitor(); verifiers.put(name, visitor); - if (name.equals("AbstractDynamicThreadPoolModule.java")) + if (name.equals("AbstractDynamicThreadPoolModule.java")) { abstractDynamicThreadPoolModuleVisitor = visitor; - if (name.equals("AsyncEventBusModuleMXBean.java")) + } + if (name.equals("AsyncEventBusModuleMXBean.java")) { asyncEventBusModuleMXBeanVisitor = visitor; - if (name.equals("AbstractNamingThreadFactoryModuleFactory.java")) + } + if (name.equals("AbstractNamingThreadFactoryModuleFactory.java")) { abstractNamingThreadFactoryModuleFactoryVisitor = visitor; - if (name.equals("AsyncEventBusModule.java")) + } + if (name.equals("AsyncEventBusModule.java")) { asyncEventBusModuleVisitor = visitor; - if (name.equals("EventBusModuleFactory.java")) + } + if (name.equals("EventBusModuleFactory.java")) { eventBusModuleFactoryVisitor = visitor; + } } processGeneratedCode(javaFiles, verifiers); @@ -348,25 +343,25 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } - private void verifyXmlFiles(Collection xmlFiles) throws Exception { + private void verifyXmlFiles(final Collection xmlFiles) throws Exception { ErrorHandler errorHandler = new ErrorHandler() { @Override - public void warning(SAXParseException exception) + public void warning(final SAXParseException exception) throws SAXException { fail("Generated blueprint xml is not well formed " + exception.getMessage()); } @Override - public void fatalError(SAXParseException exception) + public void fatalError(final SAXParseException exception) throws SAXException { fail("Generated blueprint xml is not well formed " + exception.getMessage()); } @Override - public void error(SAXParseException exception) throws SAXException { + public void error(final SAXParseException exception) throws SAXException { fail("Generated blueprint xml is not well formed " + exception.getMessage()); } @@ -386,7 +381,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } - private void assertEventBusModuleFactory(MbeASTVisitor visitor) { + private void assertEventBusModuleFactory(final MbeASTVisitor visitor) { assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX + ".threads.java", visitor.packageName); assertEquals("EventBusModuleFactory", visitor.type); @@ -406,7 +401,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { visitor.methodJavadoc.size()); } - private void assertAsyncEventBusModule(MbeASTVisitor visitor) { + private void assertAsyncEventBusModule(final MbeASTVisitor visitor) { assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX + ".threads.java", visitor.packageName); assertEquals("AsyncEventBusModule", visitor.type); @@ -427,7 +422,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } private void assertAbstractNamingThreadFactoryModuleFactory( - MbeASTVisitor visitor) { + final MbeASTVisitor visitor) { assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX + ".threads.java", visitor.packageName); assertEquals("AbstractNamingThreadFactoryModuleFactory", visitor.type); @@ -450,7 +445,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } - private void assertFactoryMethods(Set methods, int expectedSize) { + private void assertFactoryMethods(final Set methods, final int expectedSize) { List args = Lists.newArrayList(); ArgumentAssertion oldInstanceArg = new ArgumentAssertion(DynamicMBeanWithInstance.class.getCanonicalName(), "old"); @@ -496,12 +491,12 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } - private void assertMethodPresent(Set methods, MethodAssertion methodAssertion) { + private void assertMethodPresent(final Set methods, final MethodAssertion methodAssertion) { assertTrue(String.format("Generated methods did not contain %s, generated methods: %s", methodAssertion.toString(), methods), methods.contains(methodAssertion.toString())); } - private void assertAsyncEventBusModuleMXBean(MbeASTVisitor visitor) { + private void assertAsyncEventBusModuleMXBean(final MbeASTVisitor visitor) { assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX + ".threads.java", visitor.packageName); assertEquals("AsyncEventBusModuleMXBean", visitor.type); @@ -511,7 +506,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } - private void assertAbstractDynamicThreadPoolModule(MbeASTVisitor visitor) { + private void assertAbstractDynamicThreadPoolModule(final MbeASTVisitor visitor) { assertEquals(PackageTranslatorTest.EXPECTED_PACKAGE_PREFIX + ".threads.java", visitor.packageName); assertNotNull(visitor.javadoc); @@ -557,8 +552,8 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { visitor.methodJavadoc.get("void setMaximumSize(java.lang.Long maximumSize)")); } - private void assertDeclaredField(Set fieldDeclarations, - String declaration) { + private void assertDeclaredField(final Set fieldDeclarations, + final String declaration) { assertTrue("Missing field " + declaration + ", got: " + fieldDeclarations, fieldDeclarations.contains(declaration + ";\n")); @@ -570,13 +565,13 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { protected Map methodDescriptions = Maps.newHashMap(); @Override - public boolean visit(PackageDeclaration node) { + public boolean visit(final PackageDeclaration node) { packageName = node.getName().toString(); return super.visit(node); } @Override - public boolean visit(NormalAnnotation node) { + public boolean visit(final NormalAnnotation node) { if (node.getTypeName().toString() .equals(Description.class.getCanonicalName())) { if (node.getParent() instanceof TypeDeclaration) { @@ -604,7 +599,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } @Override - public boolean visit(TypeDeclaration node) { + public boolean visit(final TypeDeclaration node) { javadoc = node.getJavadoc() == null ? null : node.getJavadoc() .toString(); type = node.getName().toString(); @@ -624,7 +619,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { private final Map methodJavadoc = Maps.newHashMap(); @Override - public boolean visit(NormalAnnotation node) { + public boolean visit(final NormalAnnotation node) { boolean result = super.visit(node); if (node.getTypeName().toString() .equals(RequireInterface.class.getCanonicalName()) @@ -638,16 +633,16 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } @Override - public boolean visit(FieldDeclaration node) { + public boolean visit(final FieldDeclaration node) { fieldDeclarations.add(node.toString()); return super.visit(node); } @Override - public boolean visit(MethodDeclaration node) { - if (node.isConstructor()) + public boolean visit(final MethodDeclaration node) { + if (node.isConstructor()) { constructors.add(node.toString()); - else { + } else { String methodSignature = node.getReturnType2() + " " + node.getName() + "("; boolean first = true; for (Object o : node.parameters()) { @@ -668,7 +663,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } @Override - public boolean visit(TypeDeclaration node) { + public boolean visit(final TypeDeclaration node) { boolean visit = super.visit(node); List superIfcs = node.superInterfaceTypes(); implmts = superIfcs != null && !superIfcs.isEmpty() ? superIfcs @@ -680,14 +675,14 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { } - private void assertContains(String source, String... contained) { + private void assertContains(final String source, final String... contained) { for (String string : contained) { assertThat(source, containsString(string)); } } - private void processGeneratedCode(Collection files, - Map verifiers) throws IOException { + private void processGeneratedCode(final Collection files, + final Map verifiers) throws IOException { ASTParser parser = ASTParser.newParser(AST.JLS3); Map options = JavaCore.getOptions(); JavaCore.setComplianceOptions(JavaCore.VERSION_1_7, options); @@ -705,27 +700,31 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { for (IProblem c : cu.getProblems()) { // 1610613332 = Syntax error, annotations are only available if // source level is 5.0 - if (c.getID() == 1610613332) + if (c.getID() == 1610613332) { continue; + } // 1610613332 = Syntax error, parameterized types are only // available if source level is 5.0 - if (c.getID() == 1610613329) + if (c.getID() == 1610613329) { continue; - if (c.getID() == 1610613328) // 'for each' statements are only available if source level is 5.0 + } + if (c.getID() == 1610613328) { continue; + } fail("Error in generated source code " + file + ":" + c.getSourceLineNumber() + " id: " + c.getID() + " message:" + c.toString()); } ASTVisitor visitor = verifiers.get(file.getName()); - if (visitor == null) + if (visitor == null) { fail("Unknown generated file " + file.getName()); + } cu.accept(visitor); } } - public static char[] readFileAsChars(File file) throws IOException { + public static char[] readFileAsChars(final File file) throws IOException { List readLines = Files .readLines(file, Charset.forName("utf-8")); char[] retVal = new char[0]; @@ -741,15 +740,15 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { private static class MethodAssertion extends ArgumentAssertion{ - private List arguments; + private final List arguments; - MethodAssertion(String type, String name, List arguments) { + MethodAssertion(final String type, final String name, final List arguments) { super(type, name); this.arguments = arguments; } - MethodAssertion(String type, String name) { + MethodAssertion(final String type, final String name) { this(type, name, Collections.emptyList()); } @@ -763,8 +762,9 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { for (ArgumentAssertion argument : arguments) { sb.append(argument.type).append(' '); sb.append(argument.name); - if(++i != arguments.size()) + if(++i != arguments.size()) { sb.append(','); + } } sb.append(')'); return sb.toString(); @@ -775,7 +775,7 @@ public class JMXGeneratorTest extends AbstractGeneratorTest { protected final String type, name; - private ArgumentAssertion(String type, String name) { + private ArgumentAssertion(final String type, final String name) { this.type = type; this.name = name; } diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryPluginTest.java b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryPluginTest.java index 1c44a80e0b..d9f88643de 100644 --- a/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryPluginTest.java +++ b/opendaylight/config/yang-jmx-generator-plugin/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/ModuleMXBeanEntryPluginTest.java @@ -90,8 +90,8 @@ public class ModuleMXBeanEntryPluginTest extends ModuleMXBeanEntryTest { assertThat(runtimeBeans.size(), is(4)); { - RuntimeBeanEntry streamRB = findFirstByYangName(runtimeBeans, - "stream"); + RuntimeBeanEntry streamRB = findFirstByNamePrefix(runtimeBeans, + "ThreadStream"); assertNotNull(streamRB); assertFalse(streamRB.getKeyYangName().isPresent()); assertFalse(streamRB.getKeyJavaName().isPresent()); diff --git a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/JavaAttribute.java b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/JavaAttribute.java index f6ce92d506..6a90439bb2 100644 --- a/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/JavaAttribute.java +++ b/opendaylight/config/yang-jmx-generator/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/attribute/JavaAttribute.java @@ -201,7 +201,7 @@ public class JavaAttribute extends AbstractAttribute implements TypedAttribute { itemTypes[i++] = innerCompositeType; } - String[] descriptions = Arrays.copyOf(itemNames, itemNames.length); + String[] descriptions = itemNames.clone(); descriptions[0] = DESCRIPTION_OF_VALUE_ATTRIBUTE_FOR_UNION; try { diff --git a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryTest.java b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryTest.java index e116f480c5..50f38e3978 100644 --- a/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryTest.java +++ b/opendaylight/config/yang-jmx-generator/src/test/java/org/opendaylight/controller/config/yangjmxgenerator/ModuleMXBeanEntryTest.java @@ -140,6 +140,17 @@ public class ModuleMXBeanEntryTest extends AbstractYangTest { + " in " + runtimeBeans); } + protected RuntimeBeanEntry findFirstByNamePrefix(final Collection runtimeBeans, final String namePrefix) { + for (RuntimeBeanEntry rb : runtimeBeans) { + if (namePrefix.equals(rb.getJavaNamePrefix())) { + return rb; + } + } + + throw new IllegalArgumentException("Name prefix not found:" + namePrefix + + " in " + runtimeBeans); + } + @Test public void testGetWhenConditionMatcher() { assertMatches("config", @@ -247,8 +258,8 @@ public class ModuleMXBeanEntryTest extends AbstractYangTest { assertThat(threadRB.getRpcs().size(), is(2)); } { - RuntimeBeanEntry streamRB = findFirstByYangName(runtimeBeans, - "stream"); + RuntimeBeanEntry streamRB = findFirstByNamePrefix(runtimeBeans, + "ThreadStream"); assertNotNull(streamRB); assertFalse(streamRB.getKeyYangName().isPresent()); assertFalse(streamRB.getKeyJavaName().isPresent()); diff --git a/opendaylight/config/yang-test-plugin/pom.xml b/opendaylight/config/yang-test-plugin/pom.xml index d03cff305b..690f8d24e6 100644 --- a/opendaylight/config/yang-test-plugin/pom.xml +++ b/opendaylight/config/yang-test-plugin/pom.xml @@ -12,9 +12,6 @@ ${project.artifactId} Remove generated source files, after new files generation, implementation is inserted. - - 3.0.4 - diff --git a/opendaylight/config/yang-test/pom.xml b/opendaylight/config/yang-test/pom.xml index 5977325574..f5c966d5a6 100644 --- a/opendaylight/config/yang-test/pom.xml +++ b/opendaylight/config/yang-test/pom.xml @@ -14,9 +14,6 @@ ${project.artifactId} Artifact that contains only generated code from yang files. Suitable for testing. - - 3.0.4 - diff --git a/opendaylight/md-sal/mdsal-artifacts/pom.xml b/opendaylight/md-sal/mdsal-artifacts/pom.xml index f88e09cecb..420f888cf1 100644 --- a/opendaylight/md-sal/mdsal-artifacts/pom.xml +++ b/opendaylight/md-sal/mdsal-artifacts/pom.xml @@ -309,6 +309,18 @@ xml + + + org.opendaylight.controller + message-bus-api + ${project.version} + + + org.opendaylight.controller + message-bus-impl + ${project.version} + + diff --git a/opendaylight/md-sal/messagebus-api/pom.xml b/opendaylight/md-sal/messagebus-api/pom.xml new file mode 100644 index 0000000000..542308a96f --- /dev/null +++ b/opendaylight/md-sal/messagebus-api/pom.xml @@ -0,0 +1,104 @@ + + + + 4.0.0 + + + org.opendaylight.controller + sal-parent + 1.2.0-SNAPSHOT + + + message-bus-api + ${project.artifactId} + + bundle + + + + org.opendaylight.controller.model + model-inventory + + + org.opendaylight.yangtools.model + yang-ext + + + org.opendaylight.yangtools.model + ietf-topology + + + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + + generate-sources + + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + ${project.build.directory}/generated-sources/sal + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + ${project.build.directory}/generated-sources/config + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + org.opendaylight.yangtools.yang.unified.doc.generator.maven.DocumentationGeneratorImpl + target/site/models + + + true + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.8 + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/config + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.* + + + + + + diff --git a/opendaylight/md-sal/messagebus-api/src/main/yang/event-aggregator.yang b/opendaylight/md-sal/messagebus-api/src/main/yang/event-aggregator.yang new file mode 100644 index 0000000000..ad7b573396 --- /dev/null +++ b/opendaylight/md-sal/messagebus-api/src/main/yang/event-aggregator.yang @@ -0,0 +1,131 @@ +module event-aggregator { + // FIXME: this module needs to be split up to concepts and API + // as the concepts are shared with the other model in this + // package. + yang-version 1; + namespace "urn:cisco:params:xml:ns:yang:messagebus:eventaggregator"; + prefix "eventaggregator"; + + organization "Cisco Systems, Inc."; + contact "Robert Gallas"; + + description + "Module implementing message but RPC. + + Copyright (c)2014 Cisco Systems, Inc. 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"; + + revision "2014-12-02" { + description "Initial revision"; + } + + typedef pattern { + type string { + length 1..max; + } + + // FIXME: make this a regular expression + description "A match pattern. Specifically this is a wildcard pattern."; + } + + typedef notification-pattern { + type pattern; + description + "Pattern for matching candidate notification types. This pattern is to be + applied against the concatenation of the namespace of the module which + defines that particular notification, followed by a single colon, and + then followed by notification identifier, as supplied in the argument to + the notification statement."; + } + + typedef topic-id { + type string { + length 1..max; + } + description + "A topic identifier. It uniquely defines a topic as seen by the the user + of this model's RPCs"; + } + + // FIXME: we would really like to share instances here, but that requires some sort + // of sane reference counting. The reason for sharing is the data path part + // of notification delivery -- multiple creators of topics can still share + // a single data path. + rpc create-topic { + description + "Create a new topic. A topic is an aggregation of several notification + types from a set of nodes. Each successful invocation results in a unique + topic being created. The caller is responsible for removing the topic + once it is no longer needed."; + + input { + leaf notification-pattern { + type notification-pattern; + mandatory true; + description + "Pattern matching notification which should be forwarded into this + topic."; + } + + leaf node-id-pattern { + type pattern; + mandatory true; + description + "Pattern for matching candidate event source nodes when looking + for contributors to the topic. The pattern will be applied against + /network-topology/topology/node/node-id"; + } + } + + output { + leaf topic-id { + type topic-id; + mandatory true; + } + } + } + + rpc destroy-topic { + description + "Destroy a topic. No further messages will be delivered to it."; + + input { + leaf topic-id { + type topic-id; + mandatory true; + } + } + } + + notification topic-notification { + description + "Notification of an event occuring on a particular node. This notification + acts as an encapsulation for the event being delivered."; + + leaf topic-id { + type topic-id; + mandatory true; + description + "Topic to which this event is being delivered."; + } + + leaf node-id { + // FIXME: should be topology node ID + type string; + mandatory true; + description + "Node ID of the node which generated the event."; + } + + anyxml payload { + mandatory true; + description + "Encapsulated notification. The format is the XML representation of + a notification according to RFC6020 section 7.14.2."; + } + } +} diff --git a/opendaylight/md-sal/messagebus-api/src/main/yang/event-source.yang b/opendaylight/md-sal/messagebus-api/src/main/yang/event-source.yang new file mode 100644 index 0000000000..5dd416cde6 --- /dev/null +++ b/opendaylight/md-sal/messagebus-api/src/main/yang/event-source.yang @@ -0,0 +1,85 @@ +module event-source { + yang-version 1; + namespace "urn:cisco:params:xml:ns:yang:messagebus:eventsource"; + prefix "eventsource"; + + import event-aggregator { prefix aggr; } + import network-topology { prefix nt; revision-date "2013-10-21"; } + import opendaylight-inventory {prefix inv; revision-date "2013-08-19"; } + import yang-ext {prefix ext; revision-date "2013-07-09"; } + + organization "Cisco Systems, Inc."; + contact "Robert Gallas"; + + description + "Base model for a topology where individual nodes can produce events. + + Module implementing event source topology and encapped notification. + + Copyright (c)2014 Cisco Systems, Inc. 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"; + + revision "2014-12-02" { + description "first revision"; + } + + // FIXME: expand this + typedef join-topic-status { + type enumeration { + enum up; + enum down; + } + description "Object status"; + } + + // FIXME: migrate to topology + typedef node-ref { + type leafref { + path "/inv:nodes/inv:node/inv:id"; + } + } + + grouping topology-event-source-type { + container topology-event-source { + presence "indicates an event source-aware topology"; + } + } + + rpc join-topic { + input { + leaf node { + ext:context-reference "inv:node-context"; + type "instance-identifier"; + } + leaf topic-id { + type aggr:topic-id; + description "in current implementation notification-pattern is defined by topic-id. + By persisting topic definition we could omit notification-pattern"; + } + leaf notification-pattern { + type aggr:notification-pattern; + } + } + + output { + leaf status { + type join-topic-status; + } + } + } + + augment "/nt:network-topology/nt:topology/nt:topology-types" { + uses topology-event-source-type; + } + + augment "/nt:network-topology/nt:topology/nt:node" { + when "../../nt:topology-types/topology-event-source"; + leaf event-source-node { + type node-ref; + } + } +} diff --git a/opendaylight/md-sal/messagebus-impl/pom.xml b/opendaylight/md-sal/messagebus-impl/pom.xml new file mode 100644 index 0000000000..ccb72191c4 --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/pom.xml @@ -0,0 +1,114 @@ + + + 4.0.0 + + + org.opendaylight.controller + sal-parent + 1.2.0-SNAPSHOT + + + message-bus-impl + ${project.artifactId} + + bundle + + + + org.opendaylight.controller + ietf-netconf-notifications + + + org.opendaylight.controller + sal-binding-api + + + org.opendaylight.controller + sal-core-api + + + org.opendaylight.controller + sal-common-util + + + org.opendaylight.yangtools + yang-data-impl + + + org.opendaylight.controller + config-api + + + org.opendaylight.controller + message-bus-api + + + org.opendaylight.controller + sal-binding-config + + + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + + generate-sources + + + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + + + ${project.build.directory}/generated-sources/sal + + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + + ${project.build.directory}/generated-sources/config + + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + + org.opendaylight.yangtools.yang.unified.doc.generator.maven.DocumentationGeneratorImpl + target/site/models + + + true + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/config + + + + + + + + diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModule.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModule.java new file mode 100644 index 0000000000..1c2b78a85b --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModule.java @@ -0,0 +1,75 @@ +/** + * 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.config.yang.messagebus.app.impl; + +import org.opendaylight.controller.config.api.DependencyResolver; +import org.opendaylight.controller.config.api.ModuleIdentifier; +import org.opendaylight.controller.mdsal.InitializationContext; +import org.opendaylight.controller.mdsal.Providers; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class MessageBusAppImplModule extends org.opendaylight.controller.config.yang.messagebus.app.impl.AbstractMessageBusAppImplModule { + private static final Logger LOGGER = LoggerFactory.getLogger(MessageBusAppImplModule.class); + + private BundleContext bundleContext; + + public BundleContext getBundleContext() { + return bundleContext; + } + + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + public MessageBusAppImplModule( ModuleIdentifier identifier, DependencyResolver dependencyResolver) { + super(identifier, dependencyResolver); + } + + public MessageBusAppImplModule( ModuleIdentifier identifier, + DependencyResolver dependencyResolver, + MessageBusAppImplModule oldModule, + java.lang.AutoCloseable oldInstance) { + super(identifier, dependencyResolver, oldModule, oldInstance); + } + + @Override + protected void customValidation() {} + + @Override + public java.lang.AutoCloseable createInstance() { + List namespaceMapping = getNamespaceToStream(); + InitializationContext ic = new InitializationContext(namespaceMapping); + + final Providers.BindingAware bap = new Providers.BindingAware(ic); + final Providers.BindingIndependent bip = new Providers.BindingIndependent(ic); + + getBindingBrokerDependency().registerProvider(bap, getBundleContext()); + getDomBrokerDependency().registerProvider(bip); + + AutoCloseable closer = new AutoCloseable() { + @Override public void close() { + closeProvider(bap); + closeProvider(bip); + } + }; + + return closer; + } + + private void closeProvider(AutoCloseable closable) { + try { + closable.close(); + } catch (Exception e) { + LOGGER.error("Exception while closing: {}\n Exception: {}", closable, e); + } + } +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleFactory.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleFactory.java new file mode 100644 index 0000000000..8bee2d1d12 --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/config/yang/messagebus/app/impl/MessageBusAppImplModuleFactory.java @@ -0,0 +1,51 @@ +/* +* Generated file +* +* Generated from: yang module name: message-bus-app-impl yang module local name: messagebus-app-impl +* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator +* Generated at: Tue Feb 03 09:03:11 CET 2015 +* +* Do not modify this file unless it is present under src/main directory +*/ +package org.opendaylight.controller.config.yang.messagebus.app.impl; + +import org.opendaylight.controller.config.api.DependencyResolver; +import org.opendaylight.controller.config.api.DynamicMBeanWithInstance; +import org.opendaylight.controller.config.spi.Module; +import org.osgi.framework.BundleContext; + +public class MessageBusAppImplModuleFactory extends org.opendaylight.controller.config.yang.messagebus.app.impl.AbstractMessageBusAppImplModuleFactory { + @Override + public Module createModule(String instanceName, + DependencyResolver dependencyResolver, + BundleContext bundleContext) { + + MessageBusAppImplModule module = + (MessageBusAppImplModule) super.createModule(instanceName, + dependencyResolver, + bundleContext); + + module.setBundleContext(bundleContext); + + return module; + } + + @Override + public Module createModule(String instanceName, + DependencyResolver dependencyResolver, + DynamicMBeanWithInstance old, + BundleContext bundleContext) + throws Exception { + + MessageBusAppImplModule module = + (MessageBusAppImplModule) super.createModule(instanceName, + dependencyResolver, + old, + bundleContext); + + module.setBundleContext(bundleContext); + + return module; + } + +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/DataStore.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/DataStore.java new file mode 100644 index 0000000000..a881fac850 --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/DataStore.java @@ -0,0 +1,80 @@ +/* + * 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.mdsal; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import org.opendaylight.controller.md.sal.binding.api.DataChangeListener; +import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; +import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class DataStore { + private static final FutureCallback DEFAULT_CALLBACK = + new FutureCallback() { + public void onSuccess(Void result) { + // TODO: Implement default behaviour + } + + public void onFailure(Throwable t) { + // TODO: Implement default behaviour + }; + }; + + private final MdSAL mdSAL; + + public DataStore(MdSAL mdSAL) { + this.mdSAL = mdSAL; + } + + public ListenerRegistration registerDataChangeListener(LogicalDatastoreType store, + InstanceIdentifier path, + DataChangeListener listener, + AsyncDataBroker.DataChangeScope triggeringScope) { + return mdSAL.getDataBroker().registerDataChangeListener(store, path, listener, triggeringScope); + } + + public void asyncPUT(LogicalDatastoreType datastoreType, + InstanceIdentifier path, + T data) { + asyncPUT(datastoreType, path, data, DEFAULT_CALLBACK); + } + + public void asyncPUT(LogicalDatastoreType datastoreType, + InstanceIdentifier path, + T data, + FutureCallback callback) { + WriteTransaction tx = mdSAL.getDataBroker().newWriteOnlyTransaction(); + tx.put(datastoreType, path, data, true); + execPut(tx, callback); + } + + public T read(LogicalDatastoreType datastoreType, + InstanceIdentifier path) { + + ReadOnlyTransaction tx = mdSAL.getDataBroker().newReadOnlyTransaction(); + T result = null; + + try { + result = tx.read(datastoreType, path).get().get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return result; + } + + private static void execPut(WriteTransaction tx, FutureCallback callback) { + Futures.addCallback(tx.submit(), callback); + } +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/InitializationContext.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/InitializationContext.java new file mode 100644 index 0000000000..c73fb2ad83 --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/InitializationContext.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.mdsal; + +import org.opendaylight.controller.config.yang.messagebus.app.impl.NamespaceToStream; +import org.opendaylight.controller.messagebus.app.impl.EventAggregator; +import org.opendaylight.controller.messagebus.app.impl.EventSourceManager; +import org.opendaylight.controller.messagebus.app.impl.EventSourceTopology; +import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; +import org.opendaylight.controller.sal.core.api.Broker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class InitializationContext { + private static final Logger LOGGER = LoggerFactory.getLogger(InitializationContext.class); + + private final MdSAL mdSal; + private final DataStore dataStore; + private final EventSourceTopology eventSourceTopology; + private final EventSourceManager eventSourceManager; + private final EventAggregator eventAggregator; + + public InitializationContext(List namespaceMapping) { + this.mdSal = new MdSAL(); + this.dataStore = new DataStore(mdSal); + this.eventSourceTopology = new EventSourceTopology(dataStore); + this.eventSourceManager = new EventSourceManager(dataStore, mdSal, eventSourceTopology, namespaceMapping); + this.eventAggregator = new EventAggregator(mdSal, eventSourceTopology); + } + + public synchronized void set(BindingAwareBroker.ProviderContext session) { + mdSal.setBindingAwareContext(session); + + if (mdSal.isReady()) { + initialize(); + } + } + + public synchronized void set(Broker.ProviderSession session) { + mdSal.setBindingIndependentContext(session); + + if (mdSal.isReady()) { + initialize(); + } + } + + private void initialize() { + eventSourceTopology.mdsalReady(); + eventSourceManager.mdsalReady(); + eventAggregator.mdsalReady(); + + LOGGER.info("InitializationContext started."); + } +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/MdSAL.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/MdSAL.java new file mode 100644 index 0000000000..03b220a5fd --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/MdSAL.java @@ -0,0 +1,188 @@ +/** + * 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.mdsal; + +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService; +import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; +import org.opendaylight.controller.sal.binding.api.BindingAwareService; +import org.opendaylight.controller.sal.binding.api.mount.MountInstance; +import org.opendaylight.controller.sal.binding.api.mount.MountProviderService; +import org.opendaylight.controller.sal.core.api.Broker; +import org.opendaylight.controller.sal.core.api.BrokerService; +import org.opendaylight.controller.sal.core.api.notify.NotificationListener; +import org.opendaylight.controller.sal.core.api.notify.NotificationPublishService; +import org.opendaylight.controller.sal.core.api.notify.NotificationService; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeContext; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.RpcService; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MdSAL { + private static final Logger LOGGER = LoggerFactory.getLogger(MdSAL.class); + + private BindingAwareBroker.ProviderContext bindingAwareContext; + private Broker.ProviderSession bindingIndependentContext; + + // ----------------------------- + // ----- FRAMEWORK METHODS ----- + // ----------------------------- + public void setBindingAwareContext(BindingAwareBroker.ProviderContext bindingAwareContext) { + this.bindingAwareContext = bindingAwareContext; + } + + public void setBindingIndependentContext(Broker.ProviderSession bindingIndependentContext) { + this.bindingIndependentContext = bindingIndependentContext; + } + + //TODO: We should hide brokers and expose functionalities instead + public DataBroker getDataBroker() { + return getBaSalService(DataBroker.class); + } + + public synchronized boolean isReady() { + return (bindingAwareContext != null && bindingIndependentContext != null); + } + + // ----------------------- + // ----- API METHODS ----- + // ----------------------- + // TODO: Factor out API methods to interface + // method does not return registration object. Rather will hold references internally and manipulate using node id and API + public void addRpcImplementation(Class serviceInterface, + T implementation) + throws IllegalStateException { + bindingAwareContext.addRpcImplementation(serviceInterface, implementation); + } + + // method does not return registration object. Rather will hold references internally and manipulate using node id and API + public void addRpcImplementation(Node node, + Class serviceInterface, + T implementation) + throws IllegalStateException { + BindingAwareBroker.RoutedRpcRegistration registration + = addRoutedRpcImplementation(serviceInterface, implementation); + + NodeRef nodeRef = createNodeRef(node.getId()); + registration.registerPath(NodeContext.class, nodeRef.getValue()); + } + + public ListenerRegistration addNotificationListener(String nodeId, + QName notification, + NotificationListener listener) { + YangInstanceIdentifier yii = inventoryNodeBIIdentifier(nodeId); + + NotificationService notificationService = + getBiSalService(DOMMountPointService.class) + .getMountPoint(yii) + .get() + .getService(NotificationPublishService.class) + .get(); + + ListenerRegistration registration = + notificationService.addNotificationListener(notification, listener); + + LOGGER.info("Notification listener registered for {}, at node {}", notification, nodeId); + + return registration; + } + + public ListenerRegistration addNotificationListener(QName notification, + NotificationListener listener) { + NotificationService notificationService = + getBiSalService(NotificationPublishService.class); + + ListenerRegistration registration = + notificationService.addNotificationListener(notification, listener); + + LOGGER.info("Notification listener registered for {}.", notification); + + return registration; + } + + public T getRpcService(Class serviceInterface) { + return bindingAwareContext.getRpcService(serviceInterface); + } + + public T getRpcService(String nodeId, Class serviceInterface) { + MountProviderService mountProviderService = getBaSalService(MountProviderService.class); + + InstanceIdentifier key = InstanceIdentifier.create(Nodes.class) + .child(Node.class, + new NodeKey(new NodeId(nodeId))); + + MountInstance mountPoint = mountProviderService.getMountPoint(key); + return mountPoint.getRpcService(serviceInterface); + } + + public void publishNotification(CompositeNode notification) { + getBiSalService(NotificationPublishService.class).publish(notification); + } + + public SchemaContext getSchemaContext(String nodeId) { + YangInstanceIdentifier yii = inventoryNodeBIIdentifier(nodeId); + + SchemaContext schemaContext = + getBiSalService(DOMMountPointService.class) + .getMountPoint(yii) + .get().getSchemaContext(); + + return schemaContext; + } + + // --------------------------- + // ----- UTILITY METHODS ----- + // --------------------------- + private T getBaSalService(Class service) { + return bindingAwareContext.getSALService(service); + } + + private T getBiSalService(Class service) { + return bindingIndependentContext.getService(service); + } + + private static final String NODE_ID_NAME = "id"; + + public static YangInstanceIdentifier inventoryNodeBIIdentifier(String nodeId) { + return YangInstanceIdentifier.builder() + .node(Nodes.QNAME) + .nodeWithKey(Node.QNAME, + QName.create(Node.QNAME.getNamespace(), + Node.QNAME.getRevision(), + NODE_ID_NAME), + nodeId) + .build(); + } + + private BindingAwareBroker.RoutedRpcRegistration addRoutedRpcImplementation(Class serviceInterface, + T implementation) + throws IllegalStateException { + return bindingAwareContext.addRoutedRpcImplementation(serviceInterface, implementation); + } + + public static NodeRef createNodeRef(NodeId nodeId) { + NodeKey nodeKey = new NodeKey(nodeId); + InstanceIdentifier path = InstanceIdentifier + .builder(Nodes.class) + .child(Node.class, nodeKey) + .build(); + return new NodeRef(path); + } +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/Providers.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/Providers.java new file mode 100644 index 0000000000..a28e588d43 --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/mdsal/Providers.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.mdsal; + +import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; +import org.opendaylight.controller.sal.binding.api.BindingAwareProvider; +import org.opendaylight.controller.sal.core.api.AbstractProvider; +import org.opendaylight.controller.sal.core.api.Broker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Providers { + private static final Logger LOGGER = LoggerFactory.getLogger(Providers.class); + + public static class BindingAware implements BindingAwareProvider, AutoCloseable { + private final InitializationContext initializationContext; + + public BindingAware(InitializationContext ic) { + this.initializationContext = ic; + } + + @Override + public void onSessionInitiated(BindingAwareBroker.ProviderContext session) { + initializationContext.set(session); + + LOGGER.info("BindingAwareBroker.ProviderContext initialized"); + } + + @Override + public void close() throws Exception {} + } + + public static class BindingIndependent extends AbstractProvider implements AutoCloseable { + private final InitializationContext initializationContext; + + public BindingIndependent(InitializationContext ic) { + this.initializationContext = ic; + } + + @Override + public void onSessionInitiated(Broker.ProviderSession session) { + initializationContext.set(session); + + LOGGER.info("Broker.ProviderSession initialized"); + } + + @Override + public void close() throws Exception {} + } + +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/EventAggregator.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/EventAggregator.java new file mode 100644 index 0000000000..4b77bf2a4c --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/EventAggregator.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.messagebus.app.impl; + +import java.util.List; +import java.util.concurrent.Future; +import org.opendaylight.controller.mdsal.MdSAL; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.CreateTopicInput; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.CreateTopicOutput; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.CreateTopicOutputBuilder; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.DestroyTopicInput; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.EventAggregatorService; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.NotificationPattern; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.Node1; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO: implement topic created notification +public class EventAggregator implements EventAggregatorService { + private static final Logger LOGGER = LoggerFactory.getLogger(EventAggregator.class); + + private final MdSAL mdSAL; + private final EventSourceTopology eventSourceTopology; + + public EventAggregator(final MdSAL mdSAL, final EventSourceTopology eventSourceTopology) { + this.mdSAL = mdSAL; + this.eventSourceTopology = eventSourceTopology; + } + + public void mdsalReady() { + mdSAL.addRpcImplementation(EventAggregatorService.class, this); + } + + @Override + public Future> createTopic(final CreateTopicInput input) { + LOGGER.info("Received Topic creation request: NotificationPattern -> {}, NodeIdPattern -> {}", + input.getNotificationPattern(), + input.getNodeIdPattern()); + + Topic topic = new Topic(new NotificationPattern(input.getNotificationPattern()), input.getNodeIdPattern().getValue(), mdSAL); + + //# Make sure we capture all nodes from now on + eventSourceTopology.registerDataChangeListener(topic); + + //# Notify existing nodes + //# Code reader note: Context of Node type is NetworkTopology + List nodes = eventSourceTopology.snapshot(); + for (Node node : nodes) { + NodeId nodeIdToNotify = node.getAugmentation(Node1.class).getEventSourceNode(); + topic.notifyNode(nodeIdToNotify); + } + + CreateTopicOutput cto = new CreateTopicOutputBuilder() + .setTopicId(topic.getTopicId()) + .build(); + + return Util.resultFor(cto); + } + + @Override + public Future> destroyTopic(final DestroyTopicInput input) { + // 1. UNREGISTER DATA CHANGE LISTENER -> ? + // 2. CLOSE TOPIC + return null; + } +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/EventSourceManager.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/EventSourceManager.java new file mode 100644 index 0000000000..a84eddd458 --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/EventSourceManager.java @@ -0,0 +1,126 @@ +/* + * 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.messagebus.app.impl; + +import org.opendaylight.controller.config.yang.messagebus.app.impl.NamespaceToStream; +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.DataChangeListener; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.mdsal.DataStore; +import org.opendaylight.controller.mdsal.MdSAL; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.EventSourceService; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.inventory.rev140108.NetconfNode; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class EventSourceManager implements DataChangeListener { + private static final Logger LOGGER = LoggerFactory.getLogger(EventSourceManager.class); + private static final InstanceIdentifier INVENTORY_PATH = InstanceIdentifier.create(Nodes.class) + .child(Node.class); + private final DataStore dataStore; + private final MdSAL mdSal; + private final EventSourceTopology eventSourceTopology; + private final Map streamMap; + + public EventSourceManager(DataStore dataStore, + MdSAL mdSal, + EventSourceTopology eventSourceTopology, + List namespaceMapping) { + this.dataStore = dataStore; + this.mdSal = mdSal; + this.eventSourceTopology = eventSourceTopology; + this.streamMap = namespaceToStreamMapping(namespaceMapping); + } + + private Map namespaceToStreamMapping(List namespaceMapping) { + Map streamMap = new HashMap<>(namespaceMapping.size()); + + for (NamespaceToStream nToS : namespaceMapping) { + streamMap.put(nToS.getUrnPrefix(), nToS.getStreamName()); + } + + return streamMap; + } + + public void mdsalReady() { + dataStore.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, + INVENTORY_PATH, + this, + DataBroker.DataChangeScope.SUBTREE); + + LOGGER.info("EventSourceManager initialized."); + } + + @Override + public void onDataChanged(AsyncDataChangeEvent, DataObject> event) { + //FIXME: Prevent creating new event source on subsequent changes in inventory, like disconnect. + LOGGER.debug("[DataChangeEvent, DataObject>: {}]", event); + + Node node = Util.getAffectedNode(event); + // we listen on node tree, therefore we should rather throw IllegalStateException when node is null + if ( node == null ) { + LOGGER.debug("OnDataChanged Event. Node is null."); + return; + } + if ( isNetconfNode(node) == false ) { + LOGGER.debug("OnDataChanged Event. Not a Netconf node."); + return; + } + if ( isEventSource(node) == false ) { + LOGGER.debug("OnDataChanged Event. Node an EventSource node."); + return; + } + + NetconfEventSource netconfEventSource = new NetconfEventSource(mdSal, + node.getKey().getId().getValue(), + streamMap); + mdSal.addRpcImplementation(node, EventSourceService.class, netconfEventSource); + + InstanceIdentifier nodeInstanceIdentifier = + InstanceIdentifier.create(Nodes.class) + .child(Node.class, node.getKey()) + .augmentation(NetconfNode.class); + + dataStore.registerDataChangeListener(LogicalDatastoreType.OPERATIONAL, + nodeInstanceIdentifier, + netconfEventSource, + DataBroker.DataChangeScope.SUBTREE); + + eventSourceTopology.insert(node); + } + + private boolean isNetconfNode(Node node) { + return node.getAugmentation(NetconfNode.class) != null ; + } + + public boolean isEventSource(Node node) { + NetconfNode netconfNode = node.getAugmentation(NetconfNode.class); + + return isEventSource(netconfNode); + } + + private boolean isEventSource(NetconfNode node) { + for (String capability : node.getInitialCapability()) { + if(capability.startsWith("urn:ietf:params:xml:ns:netconf:notification")) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopology.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopology.java new file mode 100644 index 0000000000..c0700971dd --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/EventSourceTopology.java @@ -0,0 +1,106 @@ +/* + * 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.messagebus.app.impl; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.DataChangeListener; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.mdsal.DataStore; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.Node1; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.Node1Builder; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.TopologyTypes1; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.TopologyTypes1Builder; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.topology.event.source.type.TopologyEventSource; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.topology.event.source.type.TopologyEventSourceBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.TopologyTypes; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class EventSourceTopology { + private static final Logger LOGGER = LoggerFactory.getLogger(EventSourceTopology.class); + + private static final String topologyId = "EVENT-SOURCE-TOPOLOGY" ; + private static final TopologyKey topologyKey = new TopologyKey(new TopologyId(topologyId)); + private static final LogicalDatastoreType datastoreType = LogicalDatastoreType.OPERATIONAL; + + private static final InstanceIdentifier topologyInstanceIdentifier = + InstanceIdentifier.create(NetworkTopology.class) + .child(Topology.class, topologyKey); + + private static final InstanceIdentifier topologyTypeInstanceIdentifier = + topologyInstanceIdentifier + .child(TopologyTypes.class) + .augmentation(TopologyTypes1.class); + + private static final InstanceIdentifier eventSourceTopologyPath = + InstanceIdentifier.create(NetworkTopology.class) + .child(Topology.class) + .child(org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang + .network.topology.rev131021.network.topology.topology.Node.class); + + private final Map> registrations = + new ConcurrentHashMap<>(); + + private final DataStore dataStore; + + public EventSourceTopology(DataStore dataStore) { + this.dataStore = dataStore; + } + + public void mdsalReady() { + TopologyEventSource topologySource = new TopologyEventSourceBuilder().build(); + TopologyTypes1 topologyTypeAugment = new TopologyTypes1Builder().setTopologyEventSource(topologySource).build(); + + dataStore.asyncPUT(datastoreType, topologyTypeInstanceIdentifier, topologyTypeAugment); + } + + public void insert(Node node) { + String nodeId = node.getKey().getId().getValue(); + NodeKey nodeKey = new NodeKey(new NodeId(nodeId)); + InstanceIdentifier topologyNodeAugment + = topologyInstanceIdentifier + .child(org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang + .network.topology.rev131021.network.topology.topology.Node.class, nodeKey) + .augmentation(Node1.class); + + Node1 nodeAgument = new Node1Builder().setEventSourceNode(node.getId()).build(); + dataStore.asyncPUT(datastoreType, topologyNodeAugment, nodeAgument); + } + + // TODO: Should we expose this functioanlity over RPC? + public List snapshot() { + Topology topology = dataStore.read(datastoreType, topologyInstanceIdentifier); + return topology.getNode(); + } + + public void registerDataChangeListener(DataChangeListener listener) { + ListenerRegistration listenerRegistration = dataStore.registerDataChangeListener(datastoreType, + eventSourceTopologyPath, + listener, + DataBroker.DataChangeScope.SUBTREE); + + registrations.put(listener, listenerRegistration); + } +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSource.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSource.java new file mode 100644 index 0000000000..9c0697f3fb --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/NetconfEventSource.java @@ -0,0 +1,190 @@ +/* + * 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.messagebus.app.impl; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.regex.Pattern; +import org.opendaylight.controller.md.sal.binding.api.DataChangeListener; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; +import org.opendaylight.controller.mdsal.MdSAL; +import org.opendaylight.controller.sal.core.api.notify.NotificationListener; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.NotificationPattern; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.TopicNotification; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.EventSourceService; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.JoinTopicInput; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.JoinTopicOutput; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscriptionInput; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscriptionInputBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.NotificationsService; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.inventory.rev140108.NetconfNode; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +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.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NetconfEventSource implements EventSourceService, NotificationListener, DataChangeListener { + private static final Logger LOGGER = LoggerFactory.getLogger(NetconfEventSource.class); + + private final MdSAL mdSal; + private final String nodeId; + + private final List activeStreams = new ArrayList<>(); + + private final Map urnPrefixToStreamMap; + + public NetconfEventSource(final MdSAL mdSal, final String nodeId, final Map streamMap) { + Preconditions.checkNotNull(mdSal); + Preconditions.checkNotNull(nodeId); + + this.mdSal = mdSal; + this.nodeId = nodeId; + this.urnPrefixToStreamMap = streamMap; + + LOGGER.info("NetconfEventSource [{}] created.", nodeId); + } + + @Override + public Future> joinTopic(final JoinTopicInput input) { + final NotificationPattern notificationPattern = input.getNotificationPattern(); + + // FIXME: default language should already be regex + final String regex = Util.wildcardToRegex(notificationPattern.getValue()); + + final Pattern pattern = Pattern.compile(regex); + List matchingNotifications = Util.expandQname(availableNotifications(), pattern); + registerNotificationListener(matchingNotifications); + return null; + } + + private List availableNotifications() { + // FIXME: use SchemaContextListener to get changes asynchronously + Set availableNotifications = mdSal.getSchemaContext(nodeId).getNotifications(); + List qNs = new ArrayList<>(availableNotifications.size()); + for (NotificationDefinition nd : availableNotifications) { + qNs.add(nd.getQName()); + } + + return qNs; + } + + private void registerNotificationListener(final List notificationsToSubscribe) { + for (QName qName : notificationsToSubscribe) { + startSubscription(qName); + // FIXME: do not lose this registration + final ListenerRegistration reg = mdSal.addNotificationListener(nodeId, qName, this); + } + } + + private synchronized void startSubscription(final QName qName) { + String streamName = resolveStream(qName); + + if (streamIsActive(streamName) == false) { + LOGGER.info("Stream {} is not active on node {}. Will subscribe.", streamName, nodeId); + startSubscription(streamName); + } + } + + private synchronized void resubscribeToActiveStreams() { + for (String streamName : activeStreams) { + startSubscription(streamName); + } + } + + private synchronized void startSubscription(final String streamName) { + CreateSubscriptionInput subscriptionInput = getSubscriptionInput(streamName); + mdSal.getRpcService(nodeId, NotificationsService.class).createSubscription(subscriptionInput); + activeStreams.add(streamName); + } + + private static CreateSubscriptionInput getSubscriptionInput(final String streamName) { + CreateSubscriptionInputBuilder csib = new CreateSubscriptionInputBuilder(); + csib.setStream(new StreamNameType(streamName)); + return csib.build(); + } + + private String resolveStream(final QName qName) { + String streamName = null; + + for (Map.Entry entry : urnPrefixToStreamMap.entrySet()) { + String nameSpace = qName.getNamespace().toString(); + String urnPrefix = entry.getKey(); + if( nameSpace.startsWith(urnPrefix) ) { + streamName = entry.getValue(); + break; + } + } + + return streamName; + } + + private boolean streamIsActive(final String streamName) { + return activeStreams.contains(streamName); + } + + // PASS + @Override public Set getSupportedNotifications() { + return null; + } + + @Override + public void onNotification(final CompositeNode notification) { + LOGGER.info("NetconfEventSource {} received notification {}. Will publish to MD-SAL.", nodeId, notification); + ImmutableCompositeNode payload = ImmutableCompositeNode.builder() + .setQName(QName.create(TopicNotification.QNAME, "payload")) + .add(notification).toInstance(); + ImmutableCompositeNode icn = ImmutableCompositeNode.builder() + .setQName(TopicNotification.QNAME) + .add(payload) + .addLeaf("event-source", nodeId) + .toInstance(); + + mdSal.publishNotification(icn); + } + + @Override + public void onDataChanged(final AsyncDataChangeEvent, DataObject> change) { + boolean wasConnected = false; + boolean nowConnected = false; + + for (Map.Entry, DataObject> changeEntry : change.getOriginalData().entrySet()) { + if ( isNetconfNode(changeEntry) ) { + NetconfNode nn = (NetconfNode)changeEntry.getValue(); + wasConnected = nn.isConnected(); + } + } + + for (Map.Entry, DataObject> changeEntry : change.getUpdatedData().entrySet()) { + if ( isNetconfNode(changeEntry) ) { + NetconfNode nn = (NetconfNode)changeEntry.getValue(); + nowConnected = nn.isConnected(); + } + } + + if (wasConnected == false && nowConnected == true) { + resubscribeToActiveStreams(); + } + } + + private static boolean isNetconfNode(final Map.Entry, DataObject> changeEntry ) { + return NetconfNode.class.equals(changeEntry.getKey().getTargetType()); + } + +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/Topic.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/Topic.java new file mode 100644 index 0000000000..a32413064e --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/Topic.java @@ -0,0 +1,81 @@ +/* + * 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.messagebus.app.impl; + +import com.google.common.base.Preconditions; +import java.util.regex.Pattern; +import org.opendaylight.controller.md.sal.binding.api.DataChangeListener; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; +import org.opendaylight.controller.mdsal.MdSAL; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.NotificationPattern; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventaggregator.rev141202.TopicId; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.EventSourceService; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.JoinTopicInput; +import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.messagebus.eventsource.rev141202.JoinTopicInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.LoggerFactory; + +public class Topic implements DataChangeListener { + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(Topic.class); + private final NotificationPattern notificationPattern; + private final Pattern nodeIdPattern; + private final TopicId topicId; + private final MdSAL mdSal; + + public Topic(final NotificationPattern notificationPattern, final String nodeIdPattern, final MdSAL mdSal) { + this.notificationPattern = Preconditions.checkNotNull(notificationPattern); + + // FIXME: regex should be the language of nodeIdPattern + final String regex = Util.wildcardToRegex(nodeIdPattern); + this.nodeIdPattern = Pattern.compile(regex); + this.mdSal = Preconditions.checkNotNull(mdSal); + + // FIXME: We need to perform some salting in order to make + // the topic IDs less predictable. + this.topicId = new TopicId(Util.md5String(notificationPattern + nodeIdPattern)); + } + + public TopicId getTopicId() { + return topicId; + } + + @Override + public void onDataChanged(final AsyncDataChangeEvent, DataObject> event) { + // TODO: affected must return topologyNode !!! + final Node node = Util.getAffectedNode(event); + if (nodeIdPattern.matcher(node.getId().getValue()).matches()) { + notifyNode(node.getId()); + } else { + LOG.debug("Skipping node {}", node.getId()); + } + } + + public void notifyNode(final NodeId nodeId) { + JoinTopicInput jti = getJoinTopicInputArgument(nodeId); + EventSourceService ess = mdSal.getRpcService(EventSourceService.class); + Preconditions.checkState(ess != null, "EventSourceService is not registered"); + + ess.joinTopic(jti); + } + + private JoinTopicInput getJoinTopicInputArgument(final NodeId nodeId) { + NodeRef nodeRef = MdSAL.createNodeRef(nodeId); + JoinTopicInput jti = + new JoinTopicInputBuilder() + .setNode(nodeRef.getValue()) + .setTopicId(topicId) + .setNotificationPattern(notificationPattern) + .build(); + return jti; + } +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/Util.java b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/Util.java new file mode 100644 index 0000000000..9927d85c3e --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/java/org/opendaylight/controller/messagebus/app/impl/Util.java @@ -0,0 +1,137 @@ +/* + * 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.messagebus.app.impl; + +import com.google.common.util.concurrent.Futures; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.regex.Pattern; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; +import org.opendaylight.controller.sal.common.util.Rpcs; +import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; + +public final class Util { + private static final MessageDigest messageDigestTemplate = getDigestInstance(); + + private static MessageDigest getDigestInstance() { + try { + return MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Unable to get MD5 instance"); + } + } + + public static String md5String(final String inputString) { + + try { + MessageDigest md = (MessageDigest)messageDigestTemplate.clone(); + md.update(inputString.getBytes("UTF-8"), 0, inputString.length()); + return new BigInteger(1, md.digest()).toString(16); + } catch (Exception e) { + throw new RuntimeException("Unable to get MD5 instance"); + } + } + + public static Future> resultFor(final T output) { + RpcResult result = Rpcs.getRpcResult(true, output, Collections.emptyList()); + return Futures.immediateFuture(result); + } + + /** + * Extracts affected node from data change event. + * @param event + * @return + */ + public static Node getAffectedNode(final AsyncDataChangeEvent, DataObject> event) { + // TODO: expect listener method to be called even when change impact node + // TODO: test with change.getCreatedData() + for (Map.Entry, DataObject> changeEntry : event.getUpdatedData().entrySet()) { + if (isNode(changeEntry)) { + return (Node) changeEntry.getValue(); + } + } + + return null; + } + + private static boolean isNode(final Map.Entry, DataObject> changeEntry ) { + return Node.class.equals(changeEntry.getKey().getTargetType()); + } + + /** + * Method filters qnames based on wildcard strings + * + * @param availableQnames + * @param patterh matching pattern + * @return list of filtered qnames + */ + public static List expandQname(final List availableQnames, final Pattern pattern) { + List matchingQnames = new ArrayList<>(); + + for (QName qname : availableQnames) { + String namespace = qname.getNamespace().toString(); + if (pattern.matcher(namespace).matches()) { + matchingQnames.add(qname); + } + } + + return matchingQnames; + } + + /** + * CREDIT to http://www.rgagnon.com/javadetails/java-0515.html + * @param wildcard + * @return + */ + static String wildcardToRegex(final String wildcard){ + StringBuffer s = new StringBuffer(wildcard.length()); + s.append('^'); + for (char c : wildcard.toCharArray()) { + switch(c) { + case '*': + s.append(".*"); + break; + case '?': + s.append('.'); + break; + // escape special regexp-characters + case '(': + case ')': + case '[': + case ']': + case '$': + case '^': + case '.': + case '{': + case '}': + case '|': + case '\\': + s.append("\\"); + s.append(c); + break; + default: + s.append(c); + break; + } + } + s.append('$'); + return s.toString(); + } +} diff --git a/opendaylight/md-sal/messagebus-impl/src/main/yang/messagebus-app-impl.yang b/opendaylight/md-sal/messagebus-impl/src/main/yang/messagebus-app-impl.yang new file mode 100644 index 0000000000..bed6b1085a --- /dev/null +++ b/opendaylight/md-sal/messagebus-impl/src/main/yang/messagebus-app-impl.yang @@ -0,0 +1,64 @@ +module messagebus-app-impl { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:messagebus:app:impl"; + prefix "binding-impl"; + + import config { prefix config; revision-date 2013-04-05; } + import opendaylight-md-sal-binding {prefix sal;} + import opendaylight-md-sal-dom {prefix dom;} + + + description + "Service definition for Message Bus application implementation."; + + revision "2015-02-03" { + description "Second revision. Message Bus opensourcing"; + } + + identity messagebus-app-impl { + base config:module-type; + config:java-name-prefix MessageBusAppImpl; + } + + augment "/config:modules/config:module/config:configuration" { + case messagebus-app-impl { + when "/config:modules/config:module/config:type = 'messagebus-app-impl'"; + + container binding-broker { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity sal:binding-broker-osgi-registry; + } + } + } + + container dom-broker { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity dom:dom-broker-osgi-registry; + } + } + } + + list namespace-to-stream { + key urn-prefix; + + leaf urn-prefix { + type string; + } + + leaf stream-name { + type string; + } + } + } + } + + augment "/config:modules/config:module/config:state" { + case messagebus-app-impl { + when "/config:modules/config:module/config:type = 'messagebus-app-impl'"; + } + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/pom.xml b/opendaylight/md-sal/pom.xml index 9f342b88e8..bdeb8a65a8 100644 --- a/opendaylight/md-sal/pom.xml +++ b/opendaylight/md-sal/pom.xml @@ -78,6 +78,7 @@ sal-distributed-datastore + sal-dummy-distributed-datastore sal-dom-xsql @@ -89,6 +90,10 @@ sal-remoterpc-connector + + + messagebus-api + messagebus-impl diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ClientActor.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ClientActor.java index 8022e72157..fe25c75ae2 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ClientActor.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ClientActor.java @@ -11,14 +11,13 @@ package org.opendaylight.controller.cluster.example; import akka.actor.ActorRef; import akka.actor.Props; import akka.actor.UntypedActor; -import akka.event.Logging; -import akka.event.LoggingAdapter; import org.opendaylight.controller.cluster.example.messages.KeyValue; import org.opendaylight.controller.cluster.example.messages.KeyValueSaved; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ClientActor extends UntypedActor { - protected final LoggingAdapter LOG = - Logging.getLogger(getContext().system(), this); + protected final Logger LOG = LoggerFactory.getLogger(getClass()); private final ActorRef target; diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java index 9aff86ba2b..c5ae4c41b2 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/example/ExampleActor.java @@ -125,7 +125,7 @@ public class ExampleActor extends RaftActor { try { bs = fromObject(state); } catch (Exception e) { - LOG.error(e, "Exception in creating snapshot"); + LOG.error("Exception in creating snapshot", e); } getSelf().tell(new CaptureSnapshotReply(bs.toByteArray()), null); } @@ -135,7 +135,7 @@ public class ExampleActor extends RaftActor { try { state.putAll((HashMap) toObject(snapshot)); } catch (Exception e) { - LOG.error(e, "Exception in applying snapshot"); + LOG.error("Exception in applying snapshot", e); } if(LOG.isDebugEnabled()) { LOG.debug("Snapshot applied to state : {}", ((HashMap) state).size()); diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImpl.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImpl.java index d1c3fefee8..e2aa16918e 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImpl.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImpl.java @@ -41,7 +41,7 @@ public abstract class AbstractReplicatedLogImpl implements ReplicatedLog { } protected int adjustedIndex(long logEntryIndex) { - if(snapshotIndex < 0){ + if (snapshotIndex < 0) { return (int) logEntryIndex; } return (int) (logEntryIndex - (snapshotIndex + 1)); @@ -134,6 +134,11 @@ public abstract class AbstractReplicatedLogImpl implements ReplicatedLog { return journal.size(); } + @Override + public int dataSize() { + return dataSize; + } + @Override public boolean isPresent(long logEntryIndex) { if (logEntryIndex > lastIndex()) { @@ -200,6 +205,11 @@ public abstract class AbstractReplicatedLogImpl implements ReplicatedLog { previousSnapshotIndex = -1; previousSnapshotTerm = -1; dataSize = 0; + // need to recalc the datasize based on the entries left after precommit. + for(ReplicatedLogEntry logEntry : journal) { + dataSize += logEntry.size(); + } + } @Override diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ConfigParams.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ConfigParams.java index 78a1335d58..fd49737cac 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ConfigParams.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ConfigParams.java @@ -75,7 +75,7 @@ public interface ConfigParams { * The interval in which the leader needs to check itself if its isolated * @return FiniteDuration */ - FiniteDuration getIsolatedCheckInterval(); + long getIsolatedCheckIntervalInMillis(); /** diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/DefaultConfigParamsImpl.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/DefaultConfigParamsImpl.java index 86867e1d04..3e6742c17d 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/DefaultConfigParamsImpl.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/DefaultConfigParamsImpl.java @@ -42,8 +42,7 @@ public class DefaultConfigParamsImpl implements ConfigParams { private FiniteDuration heartBeatInterval = HEART_BEAT_INTERVAL; private long snapshotBatchCount = SNAPSHOT_BATCH_COUNT; private int journalRecoveryLogBatchSize = JOURNAL_RECOVERY_LOG_BATCH_SIZE; - private FiniteDuration isolatedLeaderCheckInterval = - new FiniteDuration(HEART_BEAT_INTERVAL.length() * 1000, HEART_BEAT_INTERVAL.unit()); + private long isolatedLeaderCheckInterval = HEART_BEAT_INTERVAL.$times(1000).toMillis(); // 12 is just an arbitrary percentage. This is the amount of the total memory that a raft actor's // in-memory journal can use before it needs to snapshot @@ -68,7 +67,7 @@ public class DefaultConfigParamsImpl implements ConfigParams { } public void setIsolatedLeaderCheckInterval(FiniteDuration isolatedLeaderCheckInterval) { - this.isolatedLeaderCheckInterval = isolatedLeaderCheckInterval; + this.isolatedLeaderCheckInterval = isolatedLeaderCheckInterval.toMillis(); } public void setElectionTimeoutFactor(long electionTimeoutFactor){ @@ -112,7 +111,7 @@ public class DefaultConfigParamsImpl implements ConfigParams { } @Override - public FiniteDuration getIsolatedCheckInterval() { + public long getIsolatedCheckIntervalInMillis() { return isolatedLeaderCheckInterval; } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/FollowerLogInformation.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/FollowerLogInformation.java index 6d0c14e733..73c81afd18 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/FollowerLogInformation.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/FollowerLogInformation.java @@ -73,4 +73,12 @@ public interface FollowerLogInformation { * This will stop the timeout clock */ void markFollowerInActive(); + + + /** + * This will return the active time of follower, since it was last reset + * @return time in milliseconds + */ + long timeSinceLastActivity(); + } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/FollowerLogInformationImpl.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/FollowerLogInformationImpl.java index 7a690d3d18..04b9f163f4 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/FollowerLogInformationImpl.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/FollowerLogInformationImpl.java @@ -19,7 +19,7 @@ public class FollowerLogInformationImpl implements FollowerLogInformation { private final String id; - private final Stopwatch stopwatch = new Stopwatch(); + private final Stopwatch stopwatch = Stopwatch.createUnstarted(); private final long followerTimeoutMillis; @@ -95,4 +95,21 @@ public class FollowerLogInformationImpl implements FollowerLogInformation { stopwatch.stop(); } } + + @Override + public long timeSinceLastActivity() { + return stopwatch.elapsed(TimeUnit.MILLISECONDS); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("FollowerLogInformationImpl [id=").append(id).append(", nextIndex=").append(nextIndex) + .append(", matchIndex=").append(matchIndex).append(", stopwatch=") + .append(stopwatch.elapsed(TimeUnit.MILLISECONDS)) + .append(", followerTimeoutMillis=").append(followerTimeoutMillis).append("]"); + return builder.toString(); + } + + } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java index c256c822a4..3ec8cc5c58 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActor.java @@ -10,8 +10,6 @@ package org.opendaylight.controller.cluster.raft; import akka.actor.ActorRef; import akka.actor.ActorSelection; -import akka.event.Logging; -import akka.event.LoggingAdapter; import akka.japi.Procedure; import akka.persistence.RecoveryCompleted; import akka.persistence.SaveSnapshotFailure; @@ -24,6 +22,7 @@ import com.google.common.base.Stopwatch; import com.google.protobuf.ByteString; import java.io.Serializable; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.opendaylight.controller.cluster.DataPersistenceProvider; import org.opendaylight.controller.cluster.common.actor.AbstractUntypedPersistentActor; import org.opendaylight.controller.cluster.notifications.RoleChanged; @@ -33,16 +32,15 @@ import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; import org.opendaylight.controller.cluster.raft.base.messages.Replicate; -import org.opendaylight.controller.cluster.raft.base.messages.SendHeartBeat; import org.opendaylight.controller.cluster.raft.base.messages.SendInstallSnapshot; import org.opendaylight.controller.cluster.raft.behaviors.AbstractRaftActorBehavior; import org.opendaylight.controller.cluster.raft.behaviors.Follower; import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; import org.opendaylight.controller.cluster.raft.client.messages.FindLeader; import org.opendaylight.controller.cluster.raft.client.messages.FindLeaderReply; -import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; -import org.opendaylight.controller.protobuff.messages.cluster.raft.AppendEntriesMessages; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * RaftActor encapsulates a state machine that needs to be kept synchronized @@ -85,8 +83,10 @@ import org.opendaylight.controller.protobuff.messages.cluster.raft.AppendEntries * */ public abstract class RaftActor extends AbstractUntypedPersistentActor { - protected final LoggingAdapter LOG = - Logging.getLogger(getContext().system(), this); + + private static final long APPLY_STATE_DELAY_THRESHOLD_IN_NANOS = TimeUnit.MILLISECONDS.toNanos(50L); // 50 millis + + protected final Logger LOG = LoggerFactory.getLogger(getClass()); /** * The current state determines the current behavior of a RaftActor @@ -107,14 +107,10 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { private CaptureSnapshot captureSnapshot = null; - private volatile boolean hasSnapshotCaptureInitiated = false; - private Stopwatch recoveryTimer; private int currentRecoveryBatchCount; - - public RaftActor(String id, Map peerAddresses) { this(id, peerAddresses, Optional.absent()); } @@ -131,8 +127,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { private void initRecoveryTimer() { if(recoveryTimer == null) { - recoveryTimer = new Stopwatch(); - recoveryTimer.start(); + recoveryTimer = Stopwatch.createStarted(); } } @@ -179,7 +174,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { private void onRecoveredSnapshot(SnapshotOffer offer) { if(LOG.isDebugEnabled()) { - LOG.debug("SnapshotOffer called.."); + LOG.debug("{}: SnapshotOffer called..", persistenceId()); } initRecoveryTimer(); @@ -195,8 +190,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { context.setLastApplied(snapshot.getLastAppliedIndex()); context.setCommitIndex(snapshot.getLastAppliedIndex()); - Stopwatch timer = new Stopwatch(); - timer.start(); + Stopwatch timer = Stopwatch.createStarted(); // Apply the snapshot to the actors state applyRecoverySnapshot(snapshot.getState()); @@ -209,7 +203,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { private void onRecoveredJournalLogEntry(ReplicatedLogEntry logEntry) { if(LOG.isDebugEnabled()) { - LOG.debug("Received ReplicatedLogEntry for recovery: {}", logEntry.getIndex()); + LOG.debug("{}: Received ReplicatedLogEntry for recovery: {}", persistenceId(), logEntry.getIndex()); } replicatedLog.append(logEntry); @@ -217,8 +211,8 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { private void onRecoveredApplyLogEntries(ApplyLogEntries ale) { if(LOG.isDebugEnabled()) { - LOG.debug("Received ApplyLogEntries for recovery, applying to state: {} to {}", - context.getLastApplied() + 1, ale.getToIndex()); + LOG.debug("{}: Received ApplyLogEntries for recovery, applying to state: {} to {}", + persistenceId(), context.getLastApplied() + 1, ale.getToIndex()); } for (long i = context.getLastApplied() + 1; i <= ale.getToIndex(); i++) { @@ -288,9 +282,15 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { if (message instanceof ApplyState){ ApplyState applyState = (ApplyState) message; + long elapsedTime = (System.nanoTime() - applyState.getStartTime()); + if(elapsedTime >= APPLY_STATE_DELAY_THRESHOLD_IN_NANOS){ + LOG.warn("ApplyState took more time than expected. Elapsed Time = {} ms ApplyState = {}", + TimeUnit.NANOSECONDS.toMillis(elapsedTime), applyState); + } + if(LOG.isDebugEnabled()) { - LOG.debug("Applying state for log index {} data {}", - applyState.getReplicatedLogEntry().getIndex(), + LOG.debug("{}: Applying state for log index {} data {}", + persistenceId(), applyState.getReplicatedLogEntry().getIndex(), applyState.getReplicatedLogEntry().getData()); } @@ -300,7 +300,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { } else if (message instanceof ApplyLogEntries){ ApplyLogEntries ale = (ApplyLogEntries) message; if(LOG.isDebugEnabled()) { - LOG.debug("Persisting ApplyLogEntries with index={}", ale.getToIndex()); + LOG.debug("{}: Persisting ApplyLogEntries with index={}", persistenceId(), ale.getToIndex()); } persistence().persist(new ApplyLogEntries(ale.getToIndex()), new Procedure() { @Override @@ -312,8 +312,8 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { Snapshot snapshot = ((ApplySnapshot) message).getSnapshot(); if(LOG.isDebugEnabled()) { - LOG.debug("ApplySnapshot called on Follower Actor " + - "snapshotIndex:{}, snapshotTerm:{}", snapshot.getLastAppliedIndex(), + LOG.debug("{}: ApplySnapshot called on Follower Actor " + + "snapshotIndex:{}, snapshotTerm:{}", persistenceId(), snapshot.getLastAppliedIndex(), snapshot.getLastAppliedTerm() ); } @@ -333,7 +333,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { } else if (message instanceof SaveSnapshotSuccess) { SaveSnapshotSuccess success = (SaveSnapshotSuccess) message; - LOG.info("SaveSnapshotSuccess received for snapshot"); + LOG.info("{}: SaveSnapshotSuccess received for snapshot", persistenceId()); long sequenceNumber = success.metadata().sequenceNr(); @@ -342,19 +342,19 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { } else if (message instanceof SaveSnapshotFailure) { SaveSnapshotFailure saveSnapshotFailure = (SaveSnapshotFailure) message; - LOG.info("saveSnapshotFailure.metadata():{}", saveSnapshotFailure.metadata().toString()); - LOG.error(saveSnapshotFailure.cause(), "SaveSnapshotFailure received for snapshot Cause:"); + LOG.error("{}: SaveSnapshotFailure received for snapshot Cause:", + persistenceId(), saveSnapshotFailure.cause()); context.getReplicatedLog().snapshotRollback(); - LOG.info("Replicated Log rollbacked. Snapshot will be attempted in the next cycle." + - "snapshotIndex:{}, snapshotTerm:{}, log-size:{}", + LOG.info("{}: Replicated Log rollbacked. Snapshot will be attempted in the next cycle." + + "snapshotIndex:{}, snapshotTerm:{}, log-size:{}", persistenceId(), context.getReplicatedLog().getSnapshotIndex(), context.getReplicatedLog().getSnapshotTerm(), context.getReplicatedLog().size()); } else if (message instanceof CaptureSnapshot) { - LOG.info("CaptureSnapshot received by actor"); + LOG.info("{}: CaptureSnapshot received by actor", persistenceId()); if(captureSnapshot == null) { captureSnapshot = (CaptureSnapshot)message; @@ -365,13 +365,6 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { handleCaptureSnapshotReply(((CaptureSnapshotReply) message).getSnapshot()); } else { - if (!(message instanceof AppendEntriesMessages.AppendEntries) - && !(message instanceof AppendEntriesReply) && !(message instanceof SendHeartBeat)) { - if(LOG.isDebugEnabled()) { - LOG.debug("onReceiveCommand: message: {}", message.getClass()); - } - } - RaftActorBehavior oldBehavior = currentBehavior; currentBehavior = currentBehavior.handleMessage(getSender(), message); @@ -414,7 +407,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { context.getTermInformation().getCurrentTerm(), data); if(LOG.isDebugEnabled()) { - LOG.debug("Persist data {}", replicatedLogEntry); + LOG.debug("{}: Persist data {}", persistenceId(), replicatedLogEntry); } final RaftActorContext raftContext = getRaftActorContext(); @@ -436,12 +429,13 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { self().tell(new ApplyLogEntries((int) replicatedLogEntry.getIndex()), self()); // Check if the "real" snapshot capture has been initiated. If no then do the fake snapshot - if(!hasSnapshotCaptureInitiated){ + if(!context.isSnapshotCaptureInitiated()){ raftContext.getReplicatedLog().snapshotPreCommit(raftContext.getLastApplied(), raftContext.getTermInformation().getCurrentTerm()); raftContext.getReplicatedLog().snapshotCommit(); } else { - LOG.debug("Skipping fake snapshotting for {} because real snapshotting is in progress", getId()); + LOG.debug("{}: Skipping fake snapshotting for {} because real snapshotting is in progress", + persistenceId(), getId()); } } else if (clientActor != null) { // Send message for replication @@ -579,7 +573,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { /** * This method is called during recovery to reconstruct the state of the actor. * - * @param snapshot A snapshot of the state of the actor + * @param snapshotBytes A snapshot of the state of the actor */ protected abstract void applyRecoverySnapshot(byte[] snapshotBytes); @@ -652,15 +646,15 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { } String peerAddress = context.getPeerAddress(leaderId); if(LOG.isDebugEnabled()) { - LOG.debug("getLeaderAddress leaderId = {} peerAddress = {}", - leaderId, peerAddress); + LOG.debug("{}: getLeaderAddress leaderId = {} peerAddress = {}", + persistenceId(), leaderId, peerAddress); } return peerAddress; } private void handleCaptureSnapshotReply(byte[] snapshotBytes) { - LOG.info("CaptureSnapshotReply received by actor: snapshot size {}", snapshotBytes.length); + LOG.info("{}: CaptureSnapshotReply received by actor: snapshot size {}", persistenceId(), snapshotBytes.length); // create a snapshot object from the state provided and save it // when snapshot is saved async, SaveSnapshotSuccess is raised. @@ -672,17 +666,26 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { persistence().saveSnapshot(sn); - LOG.info("Persisting of snapshot done:{}", sn.getLogMessage()); + LOG.info("{}: Persisting of snapshot done:{}", persistenceId(), sn.getLogMessage()); - //be greedy and remove entries from in-mem journal which are in the snapshot - // and update snapshotIndex and snapshotTerm without waiting for the success, + long dataThreshold = Runtime.getRuntime().totalMemory() * + getRaftActorContext().getConfigParams().getSnapshotDataThresholdPercentage() / 100; + if (context.getReplicatedLog().dataSize() > dataThreshold) { + // if memory is less, clear the log based on lastApplied. + // this could/should only happen if one of the followers is down + // as normally we keep removing from the log when its replicated to all. + context.getReplicatedLog().snapshotPreCommit(captureSnapshot.getLastAppliedIndex(), + captureSnapshot.getLastAppliedTerm()); - context.getReplicatedLog().snapshotPreCommit( - captureSnapshot.getLastAppliedIndex(), - captureSnapshot.getLastAppliedTerm()); + } else { + // clear the log based on replicatedToAllIndex + context.getReplicatedLog().snapshotPreCommit(captureSnapshot.getReplicatedToAllIndex(), + captureSnapshot.getReplicatedToAllTerm()); + } + getCurrentBehavior().setReplicatedToAllIndex(captureSnapshot.getReplicatedToAllIndex()); - LOG.info("Removed in-memory snapshotted entries, adjusted snaphsotIndex:{} " + - "and term:{}", captureSnapshot.getLastAppliedIndex(), + LOG.info("{}: Removed in-memory snapshotted entries, adjusted snaphsotIndex:{} " + + "and term:{}", persistenceId(), captureSnapshot.getLastAppliedIndex(), captureSnapshot.getLastAppliedTerm()); if (isLeader() && captureSnapshot.isInstallSnapshotInitiated()) { @@ -692,7 +695,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { } captureSnapshot = null; - hasSnapshotCaptureInitiated = false; + context.setSnapshotCaptureInitiated(false); } protected boolean hasFollowers(){ @@ -723,13 +726,14 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { // FIXME: Maybe this should be done after the command is saved journal.subList(adjustedIndex , journal.size()).clear(); - persistence().persist(new DeleteEntries(adjustedIndex), new Procedure(){ + persistence().persist(new DeleteEntries(adjustedIndex), new Procedure() { - @Override public void apply(DeleteEntries param) - throws Exception { + @Override + public void apply(DeleteEntries param) + throws Exception { //FIXME : Doing nothing for now dataSize = 0; - for(ReplicatedLogEntry entry : journal){ + for (ReplicatedLogEntry entry : journal) { dataSize += entry.size(); } } @@ -741,17 +745,12 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { appendAndPersist(replicatedLogEntry, null); } - @Override - public int dataSize() { - return dataSize; - } - public void appendAndPersist( final ReplicatedLogEntry replicatedLogEntry, final Procedure callback) { if(LOG.isDebugEnabled()) { - LOG.debug("Append log entry and persist {} ", replicatedLogEntry); + LOG.debug("{}: Append log entry and persist {} ", persistenceId(), replicatedLogEntry); } // FIXME : By adding the replicated log entry to the in-memory journal we are not truly ensuring durability of the logs @@ -772,7 +771,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { long dataSizeForCheck = dataSize; dataSizeSinceLastSnapshot += logEntrySize; - long journalSize = lastIndex()+1; + long journalSize = lastIndex() + 1; if(!hasFollowers()) { // When we do not have followers we do not maintain an in-memory log @@ -793,13 +792,15 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { getRaftActorContext().getConfigParams().getSnapshotDataThresholdPercentage() / 100; // when a snaphsot is being taken, captureSnapshot != null - if (hasSnapshotCaptureInitiated == false && + if (!context.isSnapshotCaptureInitiated() && ( journalSize % context.getConfigParams().getSnapshotBatchCount() == 0 || dataSizeForCheck > dataThreshold)) { dataSizeSinceLastSnapshot = 0; - LOG.info("Initiating Snapshot Capture.."); + LOG.info("{}: Initiating Snapshot Capture, journalSize = {}, dataSizeForCheck = {}," + + " dataThreshold = {}", persistenceId(), journalSize, dataSizeForCheck, dataThreshold); + long lastAppliedIndex = -1; long lastAppliedTerm = -1; @@ -813,20 +814,25 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { } if(LOG.isDebugEnabled()) { - LOG.debug("Snapshot Capture logSize: {}", journal.size()); - LOG.debug("Snapshot Capture lastApplied:{} ", - context.getLastApplied()); - LOG.debug("Snapshot Capture lastAppliedIndex:{}", lastAppliedIndex); - LOG.debug("Snapshot Capture lastAppliedTerm:{}", lastAppliedTerm); + LOG.debug("{}: Snapshot Capture logSize: {}", persistenceId(), journal.size()); + LOG.debug("{}: Snapshot Capture lastApplied:{} ", + persistenceId(), context.getLastApplied()); + LOG.debug("{}: Snapshot Capture lastAppliedIndex:{}", persistenceId(), + lastAppliedIndex); + LOG.debug("{}: Snapshot Capture lastAppliedTerm:{}", persistenceId(), + lastAppliedTerm); } // send a CaptureSnapshot to self to make the expensive operation async. - getSelf().tell(new CaptureSnapshot( - lastIndex(), lastTerm(), lastAppliedIndex, lastAppliedTerm), + long replicatedToAllIndex = getCurrentBehavior().getReplicatedToAllIndex(); + ReplicatedLogEntry replicatedToAllEntry = context.getReplicatedLog().get(replicatedToAllIndex); + getSelf().tell(new CaptureSnapshot(lastIndex(), lastTerm(), lastAppliedIndex, lastAppliedTerm, + (replicatedToAllEntry != null ? replicatedToAllEntry.getIndex() : -1), + (replicatedToAllEntry != null ? replicatedToAllEntry.getTerm() : -1)), null); - hasSnapshotCaptureInitiated = true; + context.setSnapshotCaptureInitiated(true); } - if(callback != null){ + if (callback != null){ callback.apply(replicatedLogEntry); } } @@ -869,7 +875,7 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { @Override public void update(long currentTerm, String votedFor) { if(LOG.isDebugEnabled()) { - LOG.debug("Set currentTerm={}, votedFor={}", currentTerm, votedFor); + LOG.debug("{}: Set currentTerm={}, votedFor={}", persistenceId(), currentTerm, votedFor); } this.currentTerm = currentTerm; this.votedFor = votedFor; diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContext.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContext.java index 0eb4b73779..9d391a1588 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContext.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContext.java @@ -12,9 +12,8 @@ import akka.actor.ActorRef; import akka.actor.ActorSelection; import akka.actor.ActorSystem; import akka.actor.Props; -import akka.event.LoggingAdapter; - import java.util.Map; +import org.slf4j.Logger; /** * The RaftActorContext contains that portion of the RaftActors state that @@ -89,7 +88,7 @@ public interface RaftActorContext { * * @param replicatedLog */ - public void setReplicatedLog(ReplicatedLog replicatedLog); + void setReplicatedLog(ReplicatedLog replicatedLog); /** * @return A representation of the log @@ -106,7 +105,7 @@ public interface RaftActorContext { * * @return */ - LoggingAdapter getLogger(); + Logger getLogger(); /** * Get a mapping of peerId's to their addresses @@ -137,7 +136,7 @@ public interface RaftActorContext { * * @param name */ - public void removePeer(String name); + void removePeer(String name); /** * Given a peerId return the corresponding actor @@ -165,5 +164,10 @@ public interface RaftActorContext { /** * @return ConfigParams */ - public ConfigParams getConfigParams(); + ConfigParams getConfigParams(); + + void setSnapshotCaptureInitiated(boolean snapshotCaptureInitiated); + + boolean isSnapshotCaptureInitiated(); + } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContextImpl.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContextImpl.java index e4aef0a844..b71b3be352 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContextImpl.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/RaftActorContextImpl.java @@ -8,16 +8,14 @@ package org.opendaylight.controller.cluster.raft; +import static com.google.common.base.Preconditions.checkState; import akka.actor.ActorRef; import akka.actor.ActorSelection; import akka.actor.ActorSystem; import akka.actor.Props; import akka.actor.UntypedActorContext; -import akka.event.LoggingAdapter; - import java.util.Map; - -import static com.google.common.base.Preconditions.checkState; +import org.slf4j.Logger; public class RaftActorContextImpl implements RaftActorContext { @@ -37,16 +35,18 @@ public class RaftActorContextImpl implements RaftActorContext { private final Map peerAddresses; - private final LoggingAdapter LOG; + private final Logger LOG; private final ConfigParams configParams; + private boolean snapshotCaptureInitiated; + public RaftActorContextImpl(ActorRef actor, UntypedActorContext context, String id, ElectionTerm termInformation, long commitIndex, long lastApplied, ReplicatedLog replicatedLog, Map peerAddresses, ConfigParams configParams, - LoggingAdapter logger) { + Logger logger) { this.actor = actor; this.context = context; this.id = id; @@ -114,7 +114,7 @@ public class RaftActorContextImpl implements RaftActorContext { return context.system(); } - @Override public LoggingAdapter getLogger() { + @Override public Logger getLogger() { return this.LOG; } @@ -130,6 +130,16 @@ public class RaftActorContextImpl implements RaftActorContext { return configParams; } + @Override + public void setSnapshotCaptureInitiated(boolean snapshotCaptureInitiated) { + this.snapshotCaptureInitiated = snapshotCaptureInitiated; + } + + @Override + public boolean isSnapshotCaptureInitiated() { + return snapshotCaptureInitiated; + } + @Override public void addToPeers(String name, String address) { peerAddresses.put(name, address); } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLog.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLog.java index 80b7ad90d0..82d0839bee 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLog.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/ReplicatedLog.java @@ -145,14 +145,14 @@ public interface ReplicatedLog { * sets snapshot term * @param snapshotTerm */ - public void setSnapshotTerm(long snapshotTerm); + void setSnapshotTerm(long snapshotTerm); /** * Clears the journal entries with startIndex(inclusive) and endIndex (exclusive) * @param startIndex * @param endIndex */ - public void clear(int startIndex, int endIndex); + void clear(int startIndex, int endIndex); /** * Handles all the bookkeeping in order to perform a rollback in the @@ -160,20 +160,21 @@ public interface ReplicatedLog { * @param snapshotCapturedIndex * @param snapshotCapturedTerm */ - public void snapshotPreCommit(long snapshotCapturedIndex, long snapshotCapturedTerm); + void snapshotPreCommit(long snapshotCapturedIndex, long snapshotCapturedTerm); /** * Sets the Replicated log to state after snapshot success. */ - public void snapshotCommit(); + void snapshotCommit(); /** * Restores the replicated log to a state in the event of a save snapshot failure */ - public void snapshotRollback(); + void snapshotRollback(); /** * Size of the data in the log (in bytes) */ - public int dataSize(); + int dataSize(); + } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/Snapshot.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/Snapshot.java index 77bf103701..feccea7edb 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/Snapshot.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/Snapshot.java @@ -12,7 +12,8 @@ import java.util.List; public class Snapshot implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -8298574936724056236L; + private final byte[] state; private final List unAppliedEntries; private final long lastIndex; diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/ApplyState.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/ApplyState.java index 0a7a632880..9299e752d1 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/ApplyState.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/ApplyState.java @@ -9,21 +9,22 @@ package org.opendaylight.controller.cluster.raft.base.messages; import akka.actor.ActorRef; -import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; - import java.io.Serializable; +import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; public class ApplyState implements Serializable { private static final long serialVersionUID = 1L; private final ActorRef clientActor; private final String identifier; private final ReplicatedLogEntry replicatedLogEntry; + private final long startTime; public ApplyState(ActorRef clientActor, String identifier, ReplicatedLogEntry replicatedLogEntry) { this.clientActor = clientActor; this.identifier = identifier; this.replicatedLogEntry = replicatedLogEntry; + this.startTime = System.nanoTime(); } public ActorRef getClientActor() { @@ -37,4 +38,17 @@ public class ApplyState implements Serializable { public ReplicatedLogEntry getReplicatedLogEntry() { return replicatedLogEntry; } + + public long getStartTime() { + return startTime; + } + + @Override + public String toString() { + return "ApplyState{" + + "identifier='" + identifier + '\'' + + ", replicatedLogEntry.index =" + replicatedLogEntry.getIndex() + + ", startTime=" + startTime + + '}'; + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CaptureSnapshot.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CaptureSnapshot.java index d4dd3350f3..a96b1e435c 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CaptureSnapshot.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CaptureSnapshot.java @@ -14,19 +14,23 @@ public class CaptureSnapshot { private long lastIndex; private long lastTerm; private boolean installSnapshotInitiated; + private long replicatedToAllIndex; + private long replicatedToAllTerm; public CaptureSnapshot(long lastIndex, long lastTerm, - long lastAppliedIndex, long lastAppliedTerm) { - this(lastIndex, lastTerm, lastAppliedIndex, lastAppliedTerm, false); + long lastAppliedIndex, long lastAppliedTerm, long replicatedToAllIndex, long replicatedToAllTerm) { + this(lastIndex, lastTerm, lastAppliedIndex, lastAppliedTerm, replicatedToAllIndex , replicatedToAllTerm, false); } public CaptureSnapshot(long lastIndex, long lastTerm,long lastAppliedIndex, - long lastAppliedTerm, boolean installSnapshotInitiated) { + long lastAppliedTerm, long replicatedToAllIndex, long replicatedToAllTerm, boolean installSnapshotInitiated) { this.lastIndex = lastIndex; this.lastTerm = lastTerm; this.lastAppliedIndex = lastAppliedIndex; this.lastAppliedTerm = lastAppliedTerm; this.installSnapshotInitiated = installSnapshotInitiated; + this.replicatedToAllIndex = replicatedToAllIndex; + this.replicatedToAllTerm = replicatedToAllTerm; } public long getLastAppliedIndex() { @@ -48,4 +52,12 @@ public class CaptureSnapshot { public boolean isInstallSnapshotInitiated() { return installSnapshotInitiated; } + + public long getReplicatedToAllIndex() { + return replicatedToAllIndex; + } + + public long getReplicatedToAllTerm() { + return replicatedToAllTerm; + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CommitEntry.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CommitEntry.java deleted file mode 100644 index 6335e3272e..0000000000 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/CommitEntry.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.controller.cluster.raft.base.messages; - -import java.io.Serializable; - -/** - * Message sent to commit an entry to the log - */ -public class CommitEntry implements Serializable { - private static final long serialVersionUID = 1L; -} diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/PersistEntry.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/PersistEntry.java deleted file mode 100644 index 68ecc1289b..0000000000 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/PersistEntry.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.controller.cluster.raft.base.messages; - -import java.io.Serializable; - -/** - * Message sent to Persist an entry into the transaction journal - */ -public class PersistEntry implements Serializable { - private static final long serialVersionUID = 1L; -} diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/SaveSnapshot.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/SaveSnapshot.java deleted file mode 100644 index 7b7f085856..0000000000 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/SaveSnapshot.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ - -package org.opendaylight.controller.cluster.raft.base.messages; - -import java.io.Serializable; - -/** - * This message is sent by a RaftActor to itself so that a subclass can process - * it and use it to save it's state - */ -public class SaveSnapshot implements Serializable { - private static final long serialVersionUID = 1L; -} diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeader.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeader.java index 462c94ec8a..be51ba069c 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeader.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeader.java @@ -26,7 +26,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.TimeUnit; import org.opendaylight.controller.cluster.raft.ClientRequestTracker; import org.opendaylight.controller.cluster.raft.ClientRequestTrackerImpl; import org.opendaylight.controller.cluster.raft.FollowerLogInformation; @@ -35,7 +34,6 @@ import org.opendaylight.controller.cluster.raft.RaftActorContext; import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; -import org.opendaylight.controller.cluster.raft.base.messages.InitiateInstallSnapshot; import org.opendaylight.controller.cluster.raft.base.messages.Replicate; import org.opendaylight.controller.cluster.raft.base.messages.SendHeartBeat; import org.opendaylight.controller.cluster.raft.base.messages.SendInstallSnapshot; @@ -94,7 +92,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { private Optional snapshot; public AbstractLeader(RaftActorContext context) { - super(context); + super(context, RaftState.Leader); final Builder ftlBuilder = ImmutableMap.builder(); for (String followerId : context.getPeerAddresses().keySet()) { @@ -109,7 +107,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { leaderId = context.getId(); - LOG.debug("Election:Leader has following peers: {}", getFollowerIds()); + LOG.debug("{}: Election: Leader has following peers: {}", logName(), getFollowerIds()); minReplicationCount = getMajorityVoteCount(getFollowerIds().size()); @@ -127,7 +125,10 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { // Upon election: send initial empty AppendEntries RPCs // (heartbeat) to each server; repeat during idle periods to // prevent election timeouts (§5.2) - scheduleHeartBeat(new FiniteDuration(0, TimeUnit.SECONDS)); + sendAppendEntries(0, false); + + // It is important to schedule this heartbeat here + scheduleHeartBeat(context.getConfigParams().getHeartBeatInterval()); } /** @@ -139,10 +140,6 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { return followerToLog.keySet(); } - private Optional getSnapshot() { - return snapshot; - } - @VisibleForTesting void setSnapshot(Optional snapshot) { this.snapshot = snapshot; @@ -152,9 +149,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { protected RaftActorBehavior handleAppendEntries(ActorRef sender, AppendEntries appendEntries) { - if(LOG.isDebugEnabled()) { - LOG.debug(appendEntries.toString()); - } + LOG.debug("{}: handleAppendEntries: {}", logName(), appendEntries); return this; } @@ -163,10 +158,10 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { protected RaftActorBehavior handleAppendEntriesReply(ActorRef sender, AppendEntriesReply appendEntriesReply) { - if(! appendEntriesReply.isSuccess()) { - if(LOG.isDebugEnabled()) { - LOG.debug(appendEntriesReply.toString()); - } + if(LOG.isTraceEnabled()) { + LOG.trace("{}: handleAppendEntriesReply: {}", logName(), appendEntriesReply); + } else if(LOG.isDebugEnabled() && !appendEntriesReply.isSuccess()) { + LOG.debug("{}: handleAppendEntriesReply: {}", logName(), appendEntriesReply); } // Update the FollowerLogInformation @@ -175,10 +170,18 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { followerToLog.get(followerId); if(followerLogInformation == null){ - LOG.error("Unknown follower {}", followerId); + LOG.error("{}: handleAppendEntriesReply - unknown follower {}", logName(), followerId); return this; } + if(followerLogInformation.timeSinceLastActivity() > + context.getConfigParams().getElectionTimeOutInterval().toMillis()) { + LOG.error("{} : handleAppendEntriesReply delayed beyond election timeout, " + + "appendEntriesReply : {}, timeSinceLastActivity : {}, lastApplied : {}, commitIndex : {}", + logName(), appendEntriesReply, followerLogInformation.timeSinceLastActivity(), + context.getLastApplied(), context.getCommitIndex()); + } + followerLogInformation.markFollowerActive(); if (appendEntriesReply.isSuccess()) { @@ -223,12 +226,34 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { // Apply the change to the state machine if (context.getCommitIndex() > context.getLastApplied()) { + LOG.debug("{}: handleAppendEntriesReply: applying to log - commitIndex: {}, lastAppliedIndex: {}", + logName(), context.getCommitIndex(), context.getLastApplied()); + applyLogToStateMachine(context.getCommitIndex()); } + if (!context.isSnapshotCaptureInitiated()) { + purgeInMemoryLog(); + } + + //Send the next log entry immediately, if possible, no need to wait for heartbeat to trigger that event + sendUpdatesToFollower(followerId, followerLogInformation, false, false); return this; } + private void purgeInMemoryLog() { + //find the lowest index across followers which has been replicated to all. + // lastApplied if there are no followers, so that we keep clearing the log for single-node + // we would delete the in-mem log from that index on, in-order to minimize mem usage + // we would also share this info thru AE with the followers so that they can delete their log entries as well. + long minReplicatedToAllIndex = followerToLog.isEmpty() ? context.getLastApplied() : Long.MAX_VALUE; + for (FollowerLogInformation info : followerToLog.values()) { + minReplicatedToAllIndex = Math.min(minReplicatedToAllIndex, info.getMatchIndex()); + } + + super.performSnapshotWithoutCapture(minReplicatedToAllIndex); + } + @Override protected ClientRequestTracker removeClientRequestTracker(long logIndex) { final Iterator it = trackerList.iterator(); @@ -259,10 +284,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { return this; } - @Override - public RaftState state() { - return RaftState.Leader; - } + protected void beforeSendHeartbeat(){} @Override public RaftActorBehavior handleMessage(ActorRef sender, Object originalMessage) { @@ -276,55 +298,62 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { // set currentTerm = T, convert to follower (§5.1) // This applies to all RPC messages and responses if (rpc.getTerm() > context.getTermInformation().getCurrentTerm()) { + LOG.debug("{}: Term {} in \"{}\" message is greater than leader's term {} - switching to Follower", + logName(), rpc.getTerm(), rpc, context.getTermInformation().getCurrentTerm()); + context.getTermInformation().updateAndPersist(rpc.getTerm(), null); return switchBehavior(new Follower(context)); } } - try { - if (message instanceof SendHeartBeat) { - sendHeartBeat(); - return this; - - } else if(message instanceof InitiateInstallSnapshot) { - installSnapshotIfNeeded(); + if (message instanceof SendHeartBeat) { + beforeSendHeartbeat(); + sendHeartBeat(); + scheduleHeartBeat(context.getConfigParams().getHeartBeatInterval()); + return this; - } else if(message instanceof SendInstallSnapshot) { - // received from RaftActor - setSnapshot(Optional.of(((SendInstallSnapshot) message).getSnapshot())); - sendInstallSnapshot(); + } else if(message instanceof SendInstallSnapshot) { + // received from RaftActor + setSnapshot(Optional.of(((SendInstallSnapshot) message).getSnapshot())); + sendInstallSnapshot(); - } else if (message instanceof Replicate) { - replicate((Replicate) message); + } else if (message instanceof Replicate) { + replicate((Replicate) message); - } else if (message instanceof InstallSnapshotReply){ - handleInstallSnapshotReply((InstallSnapshotReply) message); + } else if (message instanceof InstallSnapshotReply){ + handleInstallSnapshotReply((InstallSnapshotReply) message); - } - } finally { - scheduleHeartBeat(context.getConfigParams().getHeartBeatInterval()); } + return super.handleMessage(sender, message); } private void handleInstallSnapshotReply(InstallSnapshotReply reply) { + LOG.debug("{}: handleInstallSnapshotReply: {}", logName(), reply); + String followerId = reply.getFollowerId(); FollowerToSnapshot followerToSnapshot = mapFollowerToSnapshot.get(followerId); + + if (followerToSnapshot == null) { + LOG.error("{}: FollowerId {} in InstallSnapshotReply not known to Leader", + logName(), followerId); + return; + } + FollowerLogInformation followerLogInformation = followerToLog.get(followerId); followerLogInformation.markFollowerActive(); - if (followerToSnapshot != null && - followerToSnapshot.getChunkIndex() == reply.getChunkIndex()) { - + if (followerToSnapshot.getChunkIndex() == reply.getChunkIndex()) { + boolean wasLastChunk = false; if (reply.isSuccess()) { if(followerToSnapshot.isLastChunk(reply.getChunkIndex())) { //this was the last chunk reply if(LOG.isDebugEnabled()) { - LOG.debug("InstallSnapshotReply received, " + - "last chunk received, Chunk:{}. Follower:{} Setting nextIndex:{}", - reply.getChunkIndex(), followerId, + LOG.debug("{}: InstallSnapshotReply received, " + + "last chunk received, Chunk: {}. Follower: {} Setting nextIndex: {}", + logName(), reply.getChunkIndex(), followerId, context.getReplicatedLog().getSnapshotIndex() + 1 ); } @@ -335,35 +364,38 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { context.getReplicatedLog().getSnapshotIndex() + 1); mapFollowerToSnapshot.remove(followerId); - if(LOG.isDebugEnabled()) { - LOG.debug("followerToLog.get(followerId).getNextIndex()=" + - followerToLog.get(followerId).getNextIndex()); - } + LOG.debug("{}: follower: {}, matchIndex set to {}, nextIndex set to {}", + logName(), followerId, followerLogInformation.getMatchIndex(), + followerLogInformation.getNextIndex()); if (mapFollowerToSnapshot.isEmpty()) { // once there are no pending followers receiving snapshots // we can remove snapshot from the memory setSnapshot(Optional.absent()); } + wasLastChunk = true; } else { followerToSnapshot.markSendStatus(true); } } else { - LOG.info("InstallSnapshotReply received, " + - "sending snapshot chunk failed, Will retry, Chunk:{}", - reply.getChunkIndex() - ); + LOG.info("{}: InstallSnapshotReply received sending snapshot chunk failed, Will retry, Chunk: {}", + logName(), reply.getChunkIndex()); followerToSnapshot.markSendStatus(false); } + if (!wasLastChunk && followerToSnapshot.canSendNextChunk()) { + ActorSelection followerActor = context.getPeerActorSelection(followerId); + if(followerActor != null) { + sendSnapshotChunk(followerActor, followerId); + } + } + } else { - LOG.error("ERROR!!" + - "FollowerId in InstallSnapshotReply not known to Leader" + - " or Chunk Index in InstallSnapshotReply not matching {} != {}", - followerToSnapshot.getChunkIndex(), reply.getChunkIndex() - ); + LOG.error("{}: Chunk index {} in InstallSnapshotReply from follower {} does not match expected index {}", + logName(), reply.getChunkIndex(), followerId, + followerToSnapshot.getChunkIndex()); if(reply.getChunkIndex() == INVALID_CHUNK_INDEX){ // Since the Follower did not find this index to be valid we should reset the follower snapshot @@ -376,9 +408,8 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { private void replicate(Replicate replicate) { long logIndex = replicate.getReplicatedLogEntry().getIndex(); - if(LOG.isDebugEnabled()) { - LOG.debug("Replicate message {}", logIndex); - } + LOG.debug("{}: Replicate message: identifier: {}, logIndex: {}", logName(), + replicate.getIdentifier(), logIndex); // Create a tracker entry we will use this later to notify the // client actor @@ -392,151 +423,166 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { context.setCommitIndex(logIndex); applyLogToStateMachine(logIndex); } else { - sendAppendEntries(); + sendAppendEntries(0, false); } } - private void sendAppendEntries() { + private void sendAppendEntries(long timeSinceLastActivityInterval, boolean isHeartbeat) { // Send an AppendEntries to all followers for (Entry e : followerToLog.entrySet()) { final String followerId = e.getKey(); - ActorSelection followerActor = context.getPeerActorSelection(followerId); + final FollowerLogInformation followerLogInformation = e.getValue(); + // This checks helps not to send a repeat message to the follower + if(!followerLogInformation.isFollowerActive() || + followerLogInformation.timeSinceLastActivity() >= timeSinceLastActivityInterval) { + sendUpdatesToFollower(followerId, followerLogInformation, true, isHeartbeat); + } + } + } - if (followerActor != null) { - FollowerLogInformation followerLogInformation = followerToLog.get(followerId); - long followerNextIndex = followerLogInformation.getNextIndex(); - boolean isFollowerActive = followerLogInformation.isFollowerActive(); - - if (mapFollowerToSnapshot.get(followerId) != null) { - // if install snapshot is in process , then sent next chunk if possible - if (isFollowerActive && mapFollowerToSnapshot.get(followerId).canSendNextChunk()) { - sendSnapshotChunk(followerActor, followerId); - } else { - // we send a heartbeat even if we have not received a reply for the last chunk - sendAppendEntriesToFollower(followerActor, followerNextIndex, - Collections.emptyList()); - } + /** + * + * This method checks if any update needs to be sent to the given follower. This includes append log entries, + * sending next snapshot chunk, and initiating a snapshot. + * @return true if any update is sent, false otherwise + */ - } else { - long leaderLastIndex = context.getReplicatedLog().lastIndex(); - long leaderSnapShotIndex = context.getReplicatedLog().getSnapshotIndex(); - final List entries; - - if (isFollowerActive && - context.getReplicatedLog().isPresent(followerNextIndex)) { - // FIXME : Sending one entry at a time - entries = context.getReplicatedLog().getFrom(followerNextIndex, 1); - - } else if (isFollowerActive && followerNextIndex >= 0 && - leaderLastIndex >= followerNextIndex ) { - // if the followers next index is not present in the leaders log, and - // if the follower is just not starting and if leader's index is more than followers index - // then snapshot should be sent - - if(LOG.isDebugEnabled()) { - LOG.debug("InitiateInstallSnapshot to follower:{}," + - "follower-nextIndex:{}, leader-snapshot-index:{}, " + - "leader-last-index:{}", followerId, - followerNextIndex, leaderSnapShotIndex, leaderLastIndex - ); - } - actor().tell(new InitiateInstallSnapshot(), actor()); - - // we would want to sent AE as the capture snapshot might take time - entries = Collections.emptyList(); - - } else { - //we send an AppendEntries, even if the follower is inactive - // in-order to update the followers timestamp, in case it becomes active again - entries = Collections.emptyList(); + private void sendUpdatesToFollower(String followerId, FollowerLogInformation followerLogInformation, + boolean sendHeartbeat, boolean isHeartbeat) { + + ActorSelection followerActor = context.getPeerActorSelection(followerId); + if (followerActor != null) { + long followerNextIndex = followerLogInformation.getNextIndex(); + boolean isFollowerActive = followerLogInformation.isFollowerActive(); + + if (mapFollowerToSnapshot.get(followerId) != null) { + // if install snapshot is in process , then sent next chunk if possible + if (isFollowerActive && mapFollowerToSnapshot.get(followerId).canSendNextChunk()) { + sendSnapshotChunk(followerActor, followerId); + } else if(sendHeartbeat) { + // we send a heartbeat even if we have not received a reply for the last chunk + sendAppendEntriesToFollower(followerActor, followerLogInformation.getNextIndex(), + Collections.emptyList(), followerId); + } + } else { + long leaderLastIndex = context.getReplicatedLog().lastIndex(); + long leaderSnapShotIndex = context.getReplicatedLog().getSnapshotIndex(); + + if(!isHeartbeat || LOG.isTraceEnabled()) { + LOG.debug("{}: Checking sendAppendEntries for follower {}, leaderLastIndex: {}, leaderSnapShotIndex: {}", + logName(), followerId, leaderLastIndex, leaderSnapShotIndex); + } + + if (isFollowerActive && context.getReplicatedLog().isPresent(followerNextIndex)) { + + LOG.debug("{}: sendAppendEntries: {} is present for follower {}", logName(), + followerNextIndex, followerId); + + // FIXME : Sending one entry at a time + final List entries = context.getReplicatedLog().getFrom(followerNextIndex, 1); + + sendAppendEntriesToFollower(followerActor, followerNextIndex, entries, followerId); + + } else if (isFollowerActive && followerNextIndex >= 0 && + leaderLastIndex > followerNextIndex && !context.isSnapshotCaptureInitiated()) { + // if the followers next index is not present in the leaders log, and + // if the follower is just not starting and if leader's index is more than followers index + // then snapshot should be sent + + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("%s: InitiateInstallSnapshot to follower: %s," + + "follower-nextIndex: %d, leader-snapshot-index: %d, " + + "leader-last-index: %d", logName(), followerId, + followerNextIndex, leaderSnapShotIndex, leaderLastIndex)); } - sendAppendEntriesToFollower(followerActor, followerNextIndex, entries); + // Send heartbeat to follower whenever install snapshot is initiated. + sendAppendEntriesToFollower(followerActor, followerLogInformation.getNextIndex(), + Collections.emptyList(), followerId); + initiateCaptureSnapshot(followerId, followerNextIndex); + + } else if(sendHeartbeat) { + //we send an AppendEntries, even if the follower is inactive + // in-order to update the followers timestamp, in case it becomes active again + sendAppendEntriesToFollower(followerActor, followerLogInformation.getNextIndex(), + Collections.emptyList(), followerId); } + } } } private void sendAppendEntriesToFollower(ActorSelection followerActor, long followerNextIndex, - List entries) { - followerActor.tell( - new AppendEntries(currentTerm(), context.getId(), - prevLogIndex(followerNextIndex), - prevLogTerm(followerNextIndex), entries, - context.getCommitIndex()).toSerializable(), - actor() - ); + List entries, String followerId) { + AppendEntries appendEntries = new AppendEntries(currentTerm(), context.getId(), + prevLogIndex(followerNextIndex), + prevLogTerm(followerNextIndex), entries, + context.getCommitIndex(), super.getReplicatedToAllIndex()); + + if(!entries.isEmpty() || LOG.isTraceEnabled()) { + LOG.debug("{}: Sending AppendEntries to follower {}: {}", logName(), followerId, + appendEntries); + } + + followerActor.tell(appendEntries.toSerializable(), actor()); } /** - * An installSnapshot is scheduled at a interval that is a multiple of - * a HEARTBEAT_INTERVAL. This is to avoid the need to check for installing - * snapshots at every heartbeat. - * * Install Snapshot works as follows - * 1. Leader sends a InitiateInstallSnapshot message to self - * 2. Leader then initiates the capture snapshot by sending a CaptureSnapshot message to actor - * 3. RaftActor on receipt of the CaptureSnapshotReply (from Shard), stores the received snapshot in the replicated log + * 1. Leader initiates the capture snapshot by sending a CaptureSnapshot message to actor + * 2. RaftActor on receipt of the CaptureSnapshotReply (from Shard), stores the received snapshot in the replicated log * and makes a call to Leader's handleMessage , with SendInstallSnapshot message. - * 4. Leader , picks the snapshot from im-mem ReplicatedLog and sends it in chunks to the Follower - * 5. On complete, Follower sends back a InstallSnapshotReply. - * 6. On receipt of the InstallSnapshotReply for the last chunk, Leader marks the install complete for that follower + * 3. Leader , picks the snapshot from im-mem ReplicatedLog and sends it in chunks to the Follower + * 4. On complete, Follower sends back a InstallSnapshotReply. + * 5. On receipt of the InstallSnapshotReply for the last chunk, Leader marks the install complete for that follower * and replenishes the memory by deleting the snapshot in Replicated log. - * + * 6. If another follower requires a snapshot and a snapshot has been collected (via CaptureSnapshotReply) + * then send the existing snapshot in chunks to the follower. + * @param followerId + * @param followerNextIndex */ - private void installSnapshotIfNeeded() { - for (Entry e : followerToLog.entrySet()) { - final ActorSelection followerActor = context.getPeerActorSelection(e.getKey()); - - if (followerActor != null) { - long nextIndex = e.getValue().getNextIndex(); - - if (!context.getReplicatedLog().isPresent(nextIndex) && - context.getReplicatedLog().isInSnapshot(nextIndex)) { - LOG.info("{} follower needs a snapshot install", e.getKey()); - if (snapshot.isPresent()) { - // if a snapshot is present in the memory, most likely another install is in progress - // no need to capture snapshot - sendSnapshotChunk(followerActor, e.getKey()); - - } else { - initiateCaptureSnapshot(); - //we just need 1 follower who would need snapshot to be installed. - // when we have the snapshot captured, we would again check (in SendInstallSnapshot) - // who needs an install and send to all who need - break; - } + private void initiateCaptureSnapshot(String followerId, long followerNextIndex) { + if (!context.getReplicatedLog().isPresent(followerNextIndex) && + context.getReplicatedLog().isInSnapshot(followerNextIndex)) { + if (snapshot.isPresent()) { + // if a snapshot is present in the memory, most likely another install is in progress + // no need to capture snapshot. + // This could happen if another follower needs an install when one is going on. + final ActorSelection followerActor = context.getPeerActorSelection(followerId); + sendSnapshotChunk(followerActor, followerId); + + } else if (!context.isSnapshotCaptureInitiated()) { + + LOG.info("{}: Initiating Snapshot Capture to Install Snapshot, Leader:{}", logName(), getLeaderId()); + ReplicatedLogEntry lastAppliedEntry = context.getReplicatedLog().get(context.getLastApplied()); + long lastAppliedIndex = -1; + long lastAppliedTerm = -1; + + if (lastAppliedEntry != null) { + lastAppliedIndex = lastAppliedEntry.getIndex(); + lastAppliedTerm = lastAppliedEntry.getTerm(); + } else if (context.getReplicatedLog().getSnapshotIndex() > -1) { + lastAppliedIndex = context.getReplicatedLog().getSnapshotIndex(); + lastAppliedTerm = context.getReplicatedLog().getSnapshotTerm(); } + + boolean isInstallSnapshotInitiated = true; + long replicatedToAllIndex = super.getReplicatedToAllIndex(); + ReplicatedLogEntry replicatedToAllEntry = context.getReplicatedLog().get(replicatedToAllIndex); + actor().tell(new CaptureSnapshot(lastIndex(), lastTerm(), lastAppliedIndex, lastAppliedTerm, + (replicatedToAllEntry != null ? replicatedToAllEntry.getIndex() : -1), + (replicatedToAllEntry != null ? replicatedToAllEntry.getTerm() : -1), + isInstallSnapshotInitiated), actor()); + context.setSnapshotCaptureInitiated(true); } } } - // on every install snapshot, we try to capture the snapshot. - // Once a capture is going on, another one issued will get ignored by RaftActor. - private void initiateCaptureSnapshot() { - LOG.info("Initiating Snapshot Capture to Install Snapshot, Leader:{}", getLeaderId()); - ReplicatedLogEntry lastAppliedEntry = context.getReplicatedLog().get(context.getLastApplied()); - long lastAppliedIndex = -1; - long lastAppliedTerm = -1; - - if (lastAppliedEntry != null) { - lastAppliedIndex = lastAppliedEntry.getIndex(); - lastAppliedTerm = lastAppliedEntry.getTerm(); - } else if (context.getReplicatedLog().getSnapshotIndex() > -1) { - lastAppliedIndex = context.getReplicatedLog().getSnapshotIndex(); - lastAppliedTerm = context.getReplicatedLog().getSnapshotTerm(); - } - - boolean isInstallSnapshotInitiated = true; - actor().tell(new CaptureSnapshot(lastIndex(), lastTerm(), - lastAppliedIndex, lastAppliedTerm, isInstallSnapshotInitiated), - actor()); - } - private void sendInstallSnapshot() { + LOG.debug("{}: sendInstallSnapshot", logName()); for (Entry e : followerToLog.entrySet()) { ActorSelection followerActor = context.getPeerActorSelection(e.getKey()); @@ -558,23 +604,30 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { private void sendSnapshotChunk(ActorSelection followerActor, String followerId) { try { if (snapshot.isPresent()) { + ByteString nextSnapshotChunk = getNextSnapshotChunk(followerId,snapshot.get()); + + // Note: the previous call to getNextSnapshotChunk has the side-effect of adding + // followerId to the followerToSnapshot map. + FollowerToSnapshot followerToSnapshot = mapFollowerToSnapshot.get(followerId); + followerActor.tell( new InstallSnapshot(currentTerm(), context.getId(), context.getReplicatedLog().getSnapshotIndex(), context.getReplicatedLog().getSnapshotTerm(), - getNextSnapshotChunk(followerId,snapshot.get()), - mapFollowerToSnapshot.get(followerId).incrementChunkIndex(), - mapFollowerToSnapshot.get(followerId).getTotalChunks(), - Optional.of(mapFollowerToSnapshot.get(followerId).getLastChunkHashCode()) + nextSnapshotChunk, + followerToSnapshot.incrementChunkIndex(), + followerToSnapshot.getTotalChunks(), + Optional.of(followerToSnapshot.getLastChunkHashCode()) ).toSerializable(), actor() ); - LOG.info("InstallSnapshot sent to follower {}, Chunk: {}/{}", - followerActor.path(), mapFollowerToSnapshot.get(followerId).getChunkIndex(), - mapFollowerToSnapshot.get(followerId).getTotalChunks()); + LOG.info("{}: InstallSnapshot sent to follower {}, Chunk: {}/{}", + logName(), followerActor.path(), + followerToSnapshot.getChunkIndex(), + followerToSnapshot.getTotalChunks()); } } catch (IOException e) { - LOG.error(e, "InstallSnapshot failed for Leader."); + LOG.error("{}: InstallSnapshot failed for Leader.", logName(), e); } } @@ -589,15 +642,16 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { mapFollowerToSnapshot.put(followerId, followerToSnapshot); } ByteString nextChunk = followerToSnapshot.getNextChunk(); - if (LOG.isDebugEnabled()) { - LOG.debug("Leader's snapshot nextChunk size:{}", nextChunk.size()); - } + + LOG.debug("{}: next snapshot chunk size for follower {}: {}", logName(), followerId, nextChunk.size()); + return nextChunk; } private void sendHeartBeat() { if (!followerToLog.isEmpty()) { - sendAppendEntries(); + LOG.trace("{}: Sending heartbeat", logName()); + sendAppendEntries(context.getConfigParams().getHeartBeatInterval().toMillis(), true); } } @@ -654,14 +708,14 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { * snapshot chunks */ protected class FollowerToSnapshot { - private ByteString snapshotBytes; + private final ByteString snapshotBytes; private int offset = 0; // the next snapshot chunk is sent only if the replyReceivedForOffset matches offset private int replyReceivedForOffset; // if replyStatus is false, the previous chunk is attempted private boolean replyStatus = false; private int chunkIndex; - private int totalChunks; + private final int totalChunks; private int lastChunkHashCode = AbstractLeader.INITIAL_LAST_CHUNK_HASH_CODE; private int nextChunkHashCode = AbstractLeader.INITIAL_LAST_CHUNK_HASH_CODE; @@ -671,8 +725,8 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { totalChunks = ( size / context.getConfigParams().getSnapshotChunkSize()) + ((size % context.getConfigParams().getSnapshotChunkSize()) > 0 ? 1 : 0); if(LOG.isDebugEnabled()) { - LOG.debug("Snapshot {} bytes, total chunks to send:{}", - size, totalChunks); + LOG.debug("{}: Snapshot {} bytes, total chunks to send:{}", + logName(), size, totalChunks); } replyReceivedForOffset = -1; chunkIndex = AbstractLeader.FIRST_CHUNK_INDEX; @@ -740,10 +794,10 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { } } - if(LOG.isDebugEnabled()) { - LOG.debug("length={}, offset={},size={}", + + LOG.debug("{}: Next chunk: length={}, offset={},size={}", logName(), snapshotLength, start, size); - } + ByteString substring = getSnapshotBytes().substring(start, start + size); nextChunkHashCode = substring.hashCode(); return substring; diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehavior.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehavior.java index 04462be042..0b0b4c7cd6 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehavior.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehavior.java @@ -10,11 +10,11 @@ package org.opendaylight.controller.cluster.raft.behaviors; import akka.actor.ActorRef; import akka.actor.Cancellable; -import akka.event.LoggingAdapter; import java.util.Random; import java.util.concurrent.TimeUnit; import org.opendaylight.controller.cluster.raft.ClientRequestTracker; import org.opendaylight.controller.cluster.raft.RaftActorContext; +import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; import org.opendaylight.controller.cluster.raft.SerializationUtils; import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; @@ -24,6 +24,7 @@ import org.opendaylight.controller.cluster.raft.messages.AppendEntries; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; import org.opendaylight.controller.cluster.raft.messages.RequestVote; import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; +import org.slf4j.Logger; import scala.concurrent.duration.FiniteDuration; /** @@ -46,7 +47,7 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { /** * */ - protected final LoggingAdapter LOG; + protected final Logger LOG; /** * @@ -58,10 +59,37 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { */ protected String leaderId = null; + private long replicatedToAllIndex = -1; - protected AbstractRaftActorBehavior(RaftActorContext context) { + private final String logName; + + private final RaftState state; + + protected AbstractRaftActorBehavior(RaftActorContext context, RaftState state) { this.context = context; + this.state = state; this.LOG = context.getLogger(); + + logName = String.format("%s (%s)", context.getId(), state); + } + + @Override + public RaftState state() { + return state; + } + + public String logName() { + return logName; + } + + @Override + public void setReplicatedToAllIndex(long replicatedToAllIndex) { + this.replicatedToAllIndex = replicatedToAllIndex; + } + + @Override + public long getReplicatedToAllIndex() { + return replicatedToAllIndex; } /** @@ -94,8 +122,8 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { // 1. Reply false if term < currentTerm (§5.1) if (appendEntries.getTerm() < currentTerm()) { if(LOG.isDebugEnabled()) { - LOG.debug("Cannot append entries because sender term {} is less than {}", - appendEntries.getTerm(), currentTerm()); + LOG.debug("{}: Cannot append entries because sender term {} is less than {}", + logName(), appendEntries.getTerm(), currentTerm()); } sender.tell( @@ -132,12 +160,9 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { * @param requestVote * @return */ - protected RaftActorBehavior requestVote(ActorRef sender, - RequestVote requestVote) { + protected RaftActorBehavior requestVote(ActorRef sender, RequestVote requestVote) { - if(LOG.isDebugEnabled()) { - LOG.debug(requestVote.toString()); - } + LOG.debug("{}: In requestVote: {}", logName(), requestVote); boolean grantVote = false; @@ -173,7 +198,11 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { } } - sender.tell(new RequestVoteReply(currentTerm(), grantVote), actor()); + RequestVoteReply reply = new RequestVoteReply(currentTerm(), grantVote); + + LOG.debug("{}: requestVote returning: {}", logName(), reply); + + sender.tell(reply, actor()); return this; } @@ -349,13 +378,14 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { } else { //if one index is not present in the log, no point in looping // around as the rest wont be present either - LOG.warning( - "Missing index {} from log. Cannot apply state. Ignoring {} to {}", i, i, index); + LOG.warn( + "{}: Missing index {} from log. Cannot apply state. Ignoring {} to {}", + logName(), i, i, index); break; } } if(LOG.isDebugEnabled()) { - LOG.debug("Setting last applied to {}", newLastApplied); + LOG.debug("{}: Setting last applied to {}", logName(), newLastApplied); } context.setLastApplied(newLastApplied); @@ -389,11 +419,11 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { } protected RaftActorBehavior switchBehavior(RaftActorBehavior behavior) { - LOG.info("{} :- Switching from behavior {} to {}", context.getId(), this.state(), behavior.state()); + LOG.info("{} :- Switching from behavior {} to {}", logName(), this.state(), behavior.state()); try { close(); } catch (Exception e) { - LOG.error(e, "Failed to close behavior : {}", this.state()); + LOG.error("{}: Failed to close behavior : {}", logName(), this.state(), e); } return behavior; @@ -421,4 +451,29 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { return numMajority; } + + + /** + * Performs a snapshot with no capture on the replicated log. + * It clears the log from the supplied index or last-applied-1 which ever is minimum. + * + * @param snapshotCapturedIndex + */ + protected void performSnapshotWithoutCapture(final long snapshotCapturedIndex) { + // we would want to keep the lastApplied as its used while capturing snapshots + long lastApplied = context.getLastApplied(); + long tempMin = Math.min(snapshotCapturedIndex, (lastApplied > -1 ? lastApplied - 1 : -1)); + + if (tempMin > -1 && context.getReplicatedLog().isPresent(tempMin)) { + LOG.debug("{}: fakeSnapshot purging log to {} for term {}", logName(), tempMin, + context.getTermInformation().getCurrentTerm()); + + //use the term of the temp-min, since we check for isPresent, entry will not be null + ReplicatedLogEntry entry = context.getReplicatedLog().get(tempMin); + context.getReplicatedLog().snapshotPreCommit(tempMin, entry.getTerm()); + context.getReplicatedLog().snapshotCommit(); + setReplicatedToAllIndex(tempMin); + } + } + } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Candidate.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Candidate.java index 702417273f..b36c41abf2 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Candidate.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Candidate.java @@ -10,6 +10,7 @@ package org.opendaylight.controller.cluster.raft.behaviors; import akka.actor.ActorRef; import akka.actor.ActorSelection; +import java.util.Set; import org.opendaylight.controller.cluster.raft.RaftActorContext; import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout; @@ -19,8 +20,6 @@ import org.opendaylight.controller.cluster.raft.messages.RaftRPC; import org.opendaylight.controller.cluster.raft.messages.RequestVote; import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; -import java.util.Set; - /** * The behavior of a RaftActor when it is in the CandidateState *

@@ -48,12 +47,12 @@ public class Candidate extends AbstractRaftActorBehavior { private final Set peers; public Candidate(RaftActorContext context) { - super(context); + super(context, RaftState.Candidate); peers = context.getPeerAddresses().keySet(); if(LOG.isDebugEnabled()) { - LOG.debug("Election:Candidate has following peers: {}", peers); + LOG.debug("{}: Election: Candidate has following peers: {}", logName(), peers); } votesRequired = getMajorityVoteCount(peers.size()); @@ -66,7 +65,7 @@ public class Candidate extends AbstractRaftActorBehavior { AppendEntries appendEntries) { if(LOG.isDebugEnabled()) { - LOG.debug(appendEntries.toString()); + LOG.debug("{}: handleAppendEntries: {}", logName(), appendEntries); } return this; @@ -79,7 +78,10 @@ public class Candidate extends AbstractRaftActorBehavior { } @Override protected RaftActorBehavior handleRequestVoteReply(ActorRef sender, - RequestVoteReply requestVoteReply) { + RequestVoteReply requestVoteReply) { + + LOG.debug("{}: handleRequestVoteReply: {}, current voteCount: {}", logName(), requestVoteReply, + voteCount); if (requestVoteReply.isVoteGranted()) { voteCount++; @@ -92,10 +94,6 @@ public class Candidate extends AbstractRaftActorBehavior { return this; } - @Override public RaftState state() { - return RaftState.Candidate; - } - @Override public RaftActorBehavior handleMessage(ActorRef sender, Object originalMessage) { @@ -106,7 +104,8 @@ public class Candidate extends AbstractRaftActorBehavior { RaftRPC rpc = (RaftRPC) message; if(LOG.isDebugEnabled()) { - LOG.debug("RaftRPC message received {} my term is {}", rpc, context.getTermInformation().getCurrentTerm()); + LOG.debug("{}: RaftRPC message received {}, my term is {}", logName(), rpc, + context.getTermInformation().getCurrentTerm()); } // If RPC request or response contains term T > currentTerm: @@ -120,6 +119,8 @@ public class Candidate extends AbstractRaftActorBehavior { } if (message instanceof ElectionTimeout) { + LOG.debug("{}: Received ElectionTimeout", logName()); + if (votesRequired == 0) { // If there are no peers then we should be a Leader // We wait for the election timeout to occur before declare @@ -146,12 +147,10 @@ public class Candidate extends AbstractRaftActorBehavior { // Increment the election term and vote for self long currentTerm = context.getTermInformation().getCurrentTerm(); - context.getTermInformation().updateAndPersist(currentTerm + 1, - context.getId()); + long newTerm = currentTerm + 1; + context.getTermInformation().updateAndPersist(newTerm, context.getId()); - if(LOG.isDebugEnabled()) { - LOG.debug("Starting new term {}", (currentTerm + 1)); - } + LOG.debug("{}: Starting new term {}", logName(), newTerm); // Request for a vote // TODO: Retry request for vote if replies do not arrive in a reasonable @@ -159,17 +158,17 @@ public class Candidate extends AbstractRaftActorBehavior { for (String peerId : peers) { ActorSelection peerActor = context.getPeerActorSelection(peerId); if(peerActor != null) { - peerActor.tell(new RequestVote( + RequestVote requestVote = new RequestVote( context.getTermInformation().getCurrentTerm(), context.getId(), context.getReplicatedLog().lastIndex(), - context.getReplicatedLog().lastTerm()), - context.getActor() - ); - } - } + context.getReplicatedLog().lastTerm()); + LOG.debug("{}: Sending {} to peer {}", logName(), requestVote, peerId); + peerActor.tell(requestVote, context.getActor()); + } + } } @Override public void close() throws Exception { diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Follower.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Follower.java index cc2e55d51b..c799441d60 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Follower.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Follower.java @@ -10,7 +10,6 @@ package org.opendaylight.controller.cluster.raft.behaviors; import akka.actor.ActorRef; import com.google.common.annotations.VisibleForTesting; -import com.google.protobuf.ByteString; import java.util.ArrayList; import org.opendaylight.controller.cluster.raft.RaftActorContext; import org.opendaylight.controller.cluster.raft.RaftState; @@ -40,7 +39,7 @@ public class Follower extends AbstractRaftActorBehavior { private SnapshotTracker snapshotTracker = null; public Follower(RaftActorContext context) { - super(context); + super(context, RaftState.Follower); scheduleElection(electionDuration()); } @@ -75,10 +74,11 @@ public class Follower extends AbstractRaftActorBehavior { @Override protected RaftActorBehavior handleAppendEntries(ActorRef sender, AppendEntries appendEntries) { - if(appendEntries.getEntries() != null && appendEntries.getEntries().size() > 0) { - if(LOG.isDebugEnabled()) { - LOG.debug(appendEntries.toString()); - } + int numLogEntries = appendEntries.getEntries() != null ? appendEntries.getEntries().size() : 0; + if(LOG.isTraceEnabled()) { + LOG.trace("{}: handleAppendEntries: {}", logName(), appendEntries); + } else if(LOG.isDebugEnabled() && numLogEntries > 0) { + LOG.debug("{}: handleAppendEntries: {}", logName(), appendEntries); } // TODO : Refactor this method into a bunch of smaller methods @@ -101,44 +101,31 @@ public class Follower extends AbstractRaftActorBehavior { boolean outOfSync = true; // First check if the logs are in sync or not - if (lastIndex() == -1 - && appendEntries.getPrevLogIndex() != -1) { + long lastIndex = lastIndex(); + if (lastIndex == -1 && appendEntries.getPrevLogIndex() != -1) { // The follower's log is out of sync because the leader does have // an entry at prevLogIndex and this follower has no entries in // it's log. - if(LOG.isDebugEnabled()) { - LOG.debug("The followers log is empty and the senders prevLogIndex is {}", - appendEntries.getPrevLogIndex()); - } - - } else if (lastIndex() > -1 - && appendEntries.getPrevLogIndex() != -1 - && !prevEntryPresent) { + LOG.debug("{}: The followers log is empty and the senders prevLogIndex is {}", + logName(), appendEntries.getPrevLogIndex()); + } else if (lastIndex > -1 && appendEntries.getPrevLogIndex() != -1 && !prevEntryPresent) { // The follower's log is out of sync because the Leader's // prevLogIndex entry was not found in it's log - if(LOG.isDebugEnabled()) { - LOG.debug("The log is not empty but the prevLogIndex {} was not found in it", - appendEntries.getPrevLogIndex()); - } - - } else if (lastIndex() > -1 - && prevEntryPresent - && prevLogTerm != appendEntries.getPrevLogTerm()) { + LOG.debug("{}: The log is not empty but the prevLogIndex {} was not found in it", + logName(), appendEntries.getPrevLogIndex()); + } else if (lastIndex > -1 && prevEntryPresent && prevLogTerm != appendEntries.getPrevLogTerm()) { // The follower's log is out of sync because the Leader's // prevLogIndex entry does exist in the follower's log but it has // a different term in it - if (LOG.isDebugEnabled()) { - LOG.debug( - "Cannot append entries because previous entry term {} is not equal to append entries prevLogTerm {}" - , prevLogTerm - , appendEntries.getPrevLogTerm()); - } + LOG.debug( + "{}: Cannot append entries because previous entry term {} is not equal to append entries prevLogTerm {}", + logName(), prevLogTerm, appendEntries.getPrevLogTerm()); } else { outOfSync = false; } @@ -146,26 +133,19 @@ public class Follower extends AbstractRaftActorBehavior { if (outOfSync) { // We found that the log was out of sync so just send a negative // reply and return - if(LOG.isDebugEnabled()) { - LOG.debug("Follower ({}) is out-of-sync, " + - "so sending negative reply, lastIndex():{}, lastTerm():{}", - context.getId(), lastIndex(), lastTerm() - ); - } - sender.tell( - new AppendEntriesReply(context.getId(), currentTerm(), false, - lastIndex(), lastTerm()), actor() - ); + + LOG.debug("{}: Follower is out-of-sync, so sending negative reply, lastIndex: {}, lastTerm: {}", + logName(), lastIndex, lastTerm()); + + sender.tell(new AppendEntriesReply(context.getId(), currentTerm(), false, lastIndex, + lastTerm()), actor()); return this; } - if (appendEntries.getEntries() != null - && appendEntries.getEntries().size() > 0) { - if(LOG.isDebugEnabled()) { - LOG.debug( - "Number of entries to be appended = {}", appendEntries.getEntries().size() - ); - } + if (appendEntries.getEntries() != null && appendEntries.getEntries().size() > 0) { + + LOG.debug("{}: Number of entries to be appended = {}", logName(), + appendEntries.getEntries().size()); // 3. If an existing entry conflicts with a new one (same index // but different terms), delete the existing entry and all that @@ -183,77 +163,76 @@ public class Follower extends AbstractRaftActorBehavior { break; } - if (newEntry.getTerm() == matchEntry - .getTerm()) { + if (newEntry.getTerm() == matchEntry.getTerm()) { continue; } - if(LOG.isDebugEnabled()) { - LOG.debug( - "Removing entries from log starting at {}", matchEntry.getIndex() - ); - } + LOG.debug("{}: Removing entries from log starting at {}", logName(), + matchEntry.getIndex()); // Entries do not match so remove all subsequent entries - context.getReplicatedLog() - .removeFromAndPersist(matchEntry.getIndex()); + context.getReplicatedLog().removeFromAndPersist(matchEntry.getIndex()); break; } } - if(LOG.isDebugEnabled()) { - LOG.debug("After cleanup entries to be added from = {}", (addEntriesFrom + lastIndex()) - ); - } + lastIndex = lastIndex(); + LOG.debug("{}: After cleanup entries to be added from = {}", logName(), + (addEntriesFrom + lastIndex)); // 4. Append any new entries not already in the log - for (int i = addEntriesFrom; - i < appendEntries.getEntries().size(); i++) { + for (int i = addEntriesFrom; i < appendEntries.getEntries().size(); i++) { + ReplicatedLogEntry entry = appendEntries.getEntries().get(i); - if(LOG.isDebugEnabled()) { - LOG.debug("Append entry to log {}", appendEntries.getEntries().get(i).getData()); - } - context.getReplicatedLog().appendAndPersist(appendEntries.getEntries().get(i)); - } + LOG.debug("{}: Append entry to log {}", logName(), entry.getData()); - if(LOG.isDebugEnabled()) { - LOG.debug("Log size is now {}", context.getReplicatedLog().size()); + context.getReplicatedLog().appendAndPersist(entry); } - } + LOG.debug("{}: Log size is now {}", logName(), context.getReplicatedLog().size()); + } // 5. If leaderCommit > commitIndex, set commitIndex = // min(leaderCommit, index of last new entry) + lastIndex = lastIndex(); long prevCommitIndex = context.getCommitIndex(); - context.setCommitIndex(Math.min(appendEntries.getLeaderCommit(), - context.getReplicatedLog().lastIndex())); + context.setCommitIndex(Math.min(appendEntries.getLeaderCommit(), lastIndex)); if (prevCommitIndex != context.getCommitIndex()) { - if(LOG.isDebugEnabled()) { - LOG.debug("Commit index set to {}", context.getCommitIndex()); - } + LOG.debug("{}: Commit index set to {}", logName(), context.getCommitIndex()); } // If commitIndex > lastApplied: increment lastApplied, apply // log[lastApplied] to state machine (§5.3) // check if there are any entries to be applied. last-applied can be equal to last-index if (appendEntries.getLeaderCommit() > context.getLastApplied() && - context.getLastApplied() < lastIndex()) { + context.getLastApplied() < lastIndex) { if(LOG.isDebugEnabled()) { - LOG.debug("applyLogToStateMachine, " + - "appendEntries.getLeaderCommit():{}," + - "context.getLastApplied():{}, lastIndex():{}", - appendEntries.getLeaderCommit(), context.getLastApplied(), lastIndex() - ); + LOG.debug("{}: applyLogToStateMachine, " + + "appendEntries.getLeaderCommit(): {}," + + "context.getLastApplied(): {}, lastIndex(): {}", logName(), + appendEntries.getLeaderCommit(), context.getLastApplied(), lastIndex); } applyLogToStateMachine(appendEntries.getLeaderCommit()); } - sender.tell(new AppendEntriesReply(context.getId(), currentTerm(), true, - lastIndex(), lastTerm()), actor()); + AppendEntriesReply reply = new AppendEntriesReply(context.getId(), currentTerm(), true, + lastIndex, lastTerm()); + + if(LOG.isTraceEnabled()) { + LOG.trace("{}: handleAppendEntries returning : {}", logName(), reply); + } else if(LOG.isDebugEnabled() && numLogEntries > 0) { + LOG.debug("{}: handleAppendEntries returning : {}", logName(), reply); + } + + sender.tell(reply, actor()); + + if (!context.isSnapshotCaptureInitiated()) { + super.performSnapshotWithoutCapture(appendEntries.getReplicatedToAllIndex()); + } return this; } @@ -268,10 +247,6 @@ public class Follower extends AbstractRaftActorBehavior { return this; } - @Override public RaftState state() { - return RaftState.Follower; - } - @Override public RaftActorBehavior handleMessage(ActorRef sender, Object originalMessage) { Object message = fromSerializableMessage(originalMessage); @@ -282,11 +257,15 @@ public class Follower extends AbstractRaftActorBehavior { // set currentTerm = T, convert to follower (§5.1) // This applies to all RPC messages and responses if (rpc.getTerm() > context.getTermInformation().getCurrentTerm()) { + LOG.debug("{}: Term {} in \"{}\" message is greater than follower's term {} - updating term", + logName(), rpc.getTerm(), rpc, context.getTermInformation().getCurrentTerm()); + context.getTermInformation().updateAndPersist(rpc.getTerm(), null); } } if (message instanceof ElectionTimeout) { + LOG.debug("{}: Received ElectionTimeout - switching to Candidate", logName()); return switchBehavior(new Candidate(context)); } else if (message instanceof InstallSnapshot) { @@ -301,12 +280,10 @@ public class Follower extends AbstractRaftActorBehavior { private void handleInstallSnapshot(ActorRef sender, InstallSnapshot installSnapshot) { - if(LOG.isDebugEnabled()) { - LOG.debug("InstallSnapshot received by follower " + - "datasize:{} , Chunk:{}/{}", installSnapshot.getData().size(), - installSnapshot.getChunkIndex(), installSnapshot.getTotalChunks() - ); - } + + LOG.debug("{}: InstallSnapshot received from leader {}, datasize: {} , Chunk: {}/{}", + logName(), installSnapshot.getLeaderId(), installSnapshot.getData().size(), + installSnapshot.getChunkIndex(), installSnapshot.getTotalChunks()); if(snapshotTracker == null){ snapshotTracker = new SnapshotTracker(LOG, installSnapshot.getTotalChunks()); @@ -328,19 +305,23 @@ public class Follower extends AbstractRaftActorBehavior { } - sender.tell(new InstallSnapshotReply( - currentTerm(), context.getId(), installSnapshot.getChunkIndex(), - true), actor()); + InstallSnapshotReply reply = new InstallSnapshotReply( + currentTerm(), context.getId(), installSnapshot.getChunkIndex(), true); + + LOG.debug("{}: handleInstallSnapshot returning: {}", logName(), reply); + + sender.tell(reply, actor()); } catch (SnapshotTracker.InvalidChunkException e) { + LOG.debug("{}: Exception in InstallSnapshot of follower", logName(), e); sender.tell(new InstallSnapshotReply(currentTerm(), context.getId(), -1, false), actor()); snapshotTracker = null; } catch (Exception e){ + LOG.error("{}: Exception in InstallSnapshot of follower", logName(), e); - LOG.error(e, "Exception in InstallSnapshot of follower:"); //send reply with success as false. The chunk will be sent again on failure sender.tell(new InstallSnapshotReply(currentTerm(), context.getId(), installSnapshot.getChunkIndex(), false), actor()); @@ -348,14 +329,13 @@ public class Follower extends AbstractRaftActorBehavior { } } - @Override public void close() throws Exception { + @Override + public void close() throws Exception { stopElection(); } @VisibleForTesting - ByteString getSnapshotChunksCollected(){ - return snapshotTracker != null ? snapshotTracker.getCollectedChunks() : ByteString.EMPTY; + SnapshotTracker getSnapshotTracker(){ + return snapshotTracker; } - - } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Leader.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Leader.java index ee3cc65ddd..ebcdcd40fb 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Leader.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/Leader.java @@ -8,13 +8,12 @@ package org.opendaylight.controller.cluster.raft.behaviors; import akka.actor.ActorRef; -import akka.actor.Cancellable; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; +import java.util.concurrent.TimeUnit; import org.opendaylight.controller.cluster.raft.RaftActorContext; -import org.opendaylight.controller.cluster.raft.base.messages.InitiateInstallSnapshot; import org.opendaylight.controller.cluster.raft.base.messages.IsolatedLeaderCheck; -import scala.concurrent.duration.FiniteDuration; /** * The behavior of a RaftActor when it is in the Leader state @@ -39,17 +38,12 @@ import scala.concurrent.duration.FiniteDuration; * set commitIndex = N (§5.3, §5.4). */ public class Leader extends AbstractLeader { - private Cancellable installSnapshotSchedule = null; - private Cancellable isolatedLeaderCheckSchedule = null; + private static final IsolatedLeaderCheck ISOLATED_LEADER_CHECK = new IsolatedLeaderCheck(); + private final Stopwatch isolatedLeaderCheck; public Leader(RaftActorContext context) { super(context); - - scheduleInstallSnapshotCheck(context.getConfigParams().getIsolatedCheckInterval()); - - scheduleIsolatedLeaderCheck( - new FiniteDuration(context.getConfigParams().getHeartBeatInterval().length() * 10, - context.getConfigParams().getHeartBeatInterval().unit())); + isolatedLeaderCheck = Stopwatch.createStarted(); } @Override public RaftActorBehavior handleMessage(ActorRef sender, Object originalMessage) { @@ -57,8 +51,9 @@ public class Leader extends AbstractLeader { if (originalMessage instanceof IsolatedLeaderCheck) { if (isLeaderIsolated()) { - LOG.info("At least {} followers need to be active, Switching {} from Leader to IsolatedLeader", - minIsolatedLeaderPeerCount, leaderId); + LOG.warn("{}: At least {} followers need to be active, Switching {} from Leader to IsolatedLeader", + context.getId(), minIsolatedLeaderPeerCount, leaderId); + return switchBehavior(new IsolatedLeader(context)); } } @@ -66,46 +61,17 @@ public class Leader extends AbstractLeader { return super.handleMessage(sender, originalMessage); } - protected void stopInstallSnapshotSchedule() { - if (installSnapshotSchedule != null && !installSnapshotSchedule.isCancelled()) { - installSnapshotSchedule.cancel(); - } - } - - protected void scheduleInstallSnapshotCheck(FiniteDuration interval) { - if (getFollowerIds().isEmpty()) { - // Optimization - do not bother scheduling a heartbeat as there are - // no followers - return; - } - - stopInstallSnapshotSchedule(); - - // Schedule a message to send append entries to followers that can - // accept an append entries with some data in it - installSnapshotSchedule = - context.getActorSystem().scheduler().scheduleOnce( - interval, - context.getActor(), new InitiateInstallSnapshot(), - context.getActorSystem().dispatcher(), context.getActor()); - } - - protected void stopIsolatedLeaderCheckSchedule() { - if (isolatedLeaderCheckSchedule != null && !isolatedLeaderCheckSchedule.isCancelled()) { - isolatedLeaderCheckSchedule.cancel(); + @Override + protected void beforeSendHeartbeat(){ + if(isolatedLeaderCheck.elapsed(TimeUnit.MILLISECONDS) > context.getConfigParams().getIsolatedCheckIntervalInMillis()){ + context.getActor().tell(ISOLATED_LEADER_CHECK, context.getActor()); + isolatedLeaderCheck.reset().start(); } - } - protected void scheduleIsolatedLeaderCheck(FiniteDuration isolatedCheckInterval) { - isolatedLeaderCheckSchedule = context.getActorSystem().scheduler().schedule(isolatedCheckInterval, isolatedCheckInterval, - context.getActor(), new IsolatedLeaderCheck(), - context.getActorSystem().dispatcher(), context.getActor()); } @Override public void close() throws Exception { - stopInstallSnapshotSchedule(); - stopIsolatedLeaderCheckSchedule(); super.close(); } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/RaftActorBehavior.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/RaftActorBehavior.java index 064cd8b88c..b766e0ce39 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/RaftActorBehavior.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/RaftActorBehavior.java @@ -50,4 +50,16 @@ public interface RaftActorBehavior extends AutoCloseable{ * @return */ String getLeaderId(); + + /** + * setting the index of the log entry which is replicated to all nodes + * @param replicatedToAllIndex + */ + void setReplicatedToAllIndex(long replicatedToAllIndex); + + /** + * getting the index of the log entry which is replicated to all nodes + * @return + */ + long getReplicatedToAllIndex(); } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/SnapshotTracker.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/SnapshotTracker.java index 26fbde0711..d26837f180 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/SnapshotTracker.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/SnapshotTracker.java @@ -8,22 +8,22 @@ package org.opendaylight.controller.cluster.raft.behaviors; -import akka.event.LoggingAdapter; import com.google.common.base.Optional; import com.google.protobuf.ByteString; +import org.slf4j.Logger; /** * SnapshotTracker does house keeping for a snapshot that is being installed in chunks on the Follower */ public class SnapshotTracker { - private final LoggingAdapter LOG; + private final Logger LOG; private final int totalChunks; private ByteString collectedChunks = ByteString.EMPTY; private int lastChunkIndex = AbstractLeader.FIRST_CHUNK_INDEX - 1; private boolean sealed = false; private int lastChunkHashCode = AbstractLeader.INITIAL_LAST_CHUNK_HASH_CODE; - SnapshotTracker(LoggingAdapter LOG, int totalChunks){ + SnapshotTracker(Logger LOG, int totalChunks){ this.LOG = LOG; this.totalChunks = totalChunks; } @@ -77,6 +77,8 @@ public class SnapshotTracker { } public static class InvalidChunkException extends Exception { + private static final long serialVersionUID = 1L; + InvalidChunkException(String message){ super(message); } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/AppendEntries.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/AppendEntries.java index 8198106217..d2ea0c50cd 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/AppendEntries.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/AppendEntries.java @@ -50,14 +50,18 @@ public class AppendEntries extends AbstractRaftRPC { // leader's commitIndex private final long leaderCommit; + // index which has been replicated successfully to all followers, -1 if none + private final long replicatedToAllIndex; + public AppendEntries(long term, String leaderId, long prevLogIndex, - long prevLogTerm, List entries, long leaderCommit) { + long prevLogTerm, List entries, long leaderCommit, long replicatedToAllIndex) { super(term); this.leaderId = leaderId; this.prevLogIndex = prevLogIndex; this.prevLogTerm = prevLogTerm; this.entries = entries; this.leaderCommit = leaderCommit; + this.replicatedToAllIndex = replicatedToAllIndex; } private void writeObject(ObjectOutputStream out) throws IOException { @@ -102,18 +106,19 @@ public class AppendEntries extends AbstractRaftRPC { return leaderCommit; } + public long getReplicatedToAllIndex() { + return replicatedToAllIndex; + } + + @Override public String toString() { - final StringBuilder sb = - new StringBuilder("AppendEntries{"); - sb.append("term=").append(getTerm()); - sb.append("leaderId='").append(leaderId).append('\''); - sb.append(", prevLogIndex=").append(prevLogIndex); - sb.append(", prevLogTerm=").append(prevLogTerm); - sb.append(", entries=").append(entries); - sb.append(", leaderCommit=").append(leaderCommit); - sb.append('}'); - return sb.toString(); + StringBuilder builder = new StringBuilder(); + builder.append("AppendEntries [term=").append(term).append(", leaderId=").append(leaderId) + .append(", prevLogIndex=").append(prevLogIndex).append(", prevLogTerm=").append(prevLogTerm) + .append(", entries=").append(entries).append(", leaderCommit=").append(leaderCommit) + .append(", replicatedToAllIndex=").append(replicatedToAllIndex).append("]"); + return builder.toString(); } public Object toSerializable() { @@ -203,7 +208,7 @@ public class AppendEntries extends AbstractRaftRPC { from.getPrevLogIndex(), from.getPrevLogTerm(), logEntryList, - from.getLeaderCommit()); + from.getLeaderCommit(), -1); return to; } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/AppendEntriesReply.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/AppendEntriesReply.java index a782eda565..01fef006a9 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/AppendEntriesReply.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/AppendEntriesReply.java @@ -12,7 +12,7 @@ package org.opendaylight.controller.cluster.raft.messages; * Reply for the AppendEntriesRpc message */ public class AppendEntriesReply extends AbstractRaftRPC { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -7487547356392536683L; // true if follower contained entry matching // prevLogIndex and prevLogTerm @@ -38,6 +38,7 @@ public class AppendEntriesReply extends AbstractRaftRPC { this.logLastTerm = logLastTerm; } + @Override public long getTerm() { return term; } @@ -58,15 +59,12 @@ public class AppendEntriesReply extends AbstractRaftRPC { return followerId; } - @Override public String toString() { - final StringBuilder sb = - new StringBuilder("AppendEntriesReply{"); - sb.append("term=").append(term); - sb.append(", success=").append(success); - sb.append(", logLastIndex=").append(logLastIndex); - sb.append(", logLastTerm=").append(logLastTerm); - sb.append(", followerId='").append(followerId).append('\''); - sb.append('}'); - return sb.toString(); + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AppendEntriesReply [term=").append(term).append(", success=").append(success) + .append(", logLastIndex=").append(logLastIndex).append(", logLastTerm=").append(logLastTerm) + .append(", followerId=").append(followerId).append("]"); + return builder.toString(); } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/InstallSnapshot.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/InstallSnapshot.java index 6337f8f6dc..13636f36d7 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/InstallSnapshot.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/InstallSnapshot.java @@ -73,6 +73,7 @@ public class InstallSnapshot extends AbstractRaftRPC { public Object toSerializable(){ InstallSnapshotMessages.InstallSnapshot.Builder builder = InstallSnapshotMessages.InstallSnapshot.newBuilder() + .setTerm(this.getTerm()) .setLeaderId(this.getLeaderId()) .setChunkIndex(this.getChunkIndex()) .setData(this.getData()) @@ -102,4 +103,15 @@ public class InstallSnapshot extends AbstractRaftRPC { return installSnapshot; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("InstallSnapshot [term=").append(term).append(", leaderId=").append(leaderId) + .append(", lastIncludedIndex=").append(lastIncludedIndex).append(", lastIncludedTerm=") + .append(lastIncludedTerm).append(", data=").append(data).append(", chunkIndex=").append(chunkIndex) + .append(", totalChunks=").append(totalChunks).append(", lastChunkHashCode=").append(lastChunkHashCode) + .append("]"); + return builder.toString(); + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/InstallSnapshotReply.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/InstallSnapshotReply.java index 71e7ecc189..77efa53dfd 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/InstallSnapshotReply.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/InstallSnapshotReply.java @@ -9,13 +9,13 @@ package org.opendaylight.controller.cluster.raft.messages; public class InstallSnapshotReply extends AbstractRaftRPC { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 642227896390779503L; // The followerId - this will be used to figure out which follower is // responding private final String followerId; private final int chunkIndex; - private boolean success; + private final boolean success; public InstallSnapshotReply(long term, String followerId, int chunkIndex, boolean success) { @@ -36,4 +36,12 @@ public class InstallSnapshotReply extends AbstractRaftRPC { public boolean isSuccess() { return success; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("InstallSnapshotReply [term=").append(term).append(", followerId=").append(followerId) + .append(", chunkIndex=").append(chunkIndex).append(", success=").append(success).append("]"); + return builder.toString(); + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/RequestVote.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/RequestVote.java index 8321d0c25b..8f162ae254 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/RequestVote.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/RequestVote.java @@ -12,7 +12,7 @@ package org.opendaylight.controller.cluster.raft.messages; * Invoked by candidates to gather votes (§5.2). */ public class RequestVote extends AbstractRaftRPC { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -6967509186297108657L; // candidate requesting vote private String candidateId; @@ -35,6 +35,7 @@ public class RequestVote extends AbstractRaftRPC { public RequestVote() { } + @Override public long getTerm() { return term; } @@ -63,14 +64,12 @@ public class RequestVote extends AbstractRaftRPC { this.lastLogTerm = lastLogTerm; } - @Override public String toString() { - final StringBuilder sb = - new StringBuilder("RequestVote{"); - sb.append("term='").append(getTerm()).append('\''); - sb.append("candidateId='").append(candidateId).append('\''); - sb.append(", lastLogIndex=").append(lastLogIndex); - sb.append(", lastLogTerm=").append(lastLogTerm); - sb.append('}'); - return sb.toString(); + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RequestVote [term=").append(term).append(", candidateId=").append(candidateId) + .append(", lastLogIndex=").append(lastLogIndex).append(", lastLogTerm=").append(lastLogTerm) + .append("]"); + return builder.toString(); } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/RequestVoteReply.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/RequestVoteReply.java index da3ba5c39f..865d4c287b 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/RequestVoteReply.java +++ b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/messages/RequestVoteReply.java @@ -9,7 +9,7 @@ package org.opendaylight.controller.cluster.raft.messages; public class RequestVoteReply extends AbstractRaftRPC { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 8427899326488775660L; // true means candidate received vot private final boolean voteGranted; @@ -19,6 +19,7 @@ public class RequestVoteReply extends AbstractRaftRPC { this.voteGranted = voteGranted; } + @Override public long getTerm() { return term; } @@ -26,4 +27,11 @@ public class RequestVoteReply extends AbstractRaftRPC { public boolean isVoteGranted() { return voteGranted; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("RequestVoteReply [term=").append(term).append(", voteGranted=").append(voteGranted).append("]"); + return builder.toString(); + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImplTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImplTest.java index d53ccf2500..885c3ab109 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImplTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/AbstractReplicatedLogImplTest.java @@ -20,6 +20,7 @@ import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload; import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockReplicatedLogEntry; + /** * */ @@ -128,6 +129,63 @@ public class AbstractReplicatedLogImplTest { } + @Test + public void testSnapshotPreCommit() { + //add 4 more entries + replicatedLogImpl.append(new MockReplicatedLogEntry(2, 4, new MockPayload("E"))); + replicatedLogImpl.append(new MockReplicatedLogEntry(2, 5, new MockPayload("F"))); + replicatedLogImpl.append(new MockReplicatedLogEntry(3, 6, new MockPayload("G"))); + replicatedLogImpl.append(new MockReplicatedLogEntry(3, 7, new MockPayload("H"))); + + //sending negative values should not cause any changes + replicatedLogImpl.snapshotPreCommit(-1, -1); + assertEquals(8, replicatedLogImpl.size()); + assertEquals(-1, replicatedLogImpl.getSnapshotIndex()); + + replicatedLogImpl.snapshotPreCommit(4, 3); + assertEquals(3, replicatedLogImpl.size()); + assertEquals(4, replicatedLogImpl.getSnapshotIndex()); + + replicatedLogImpl.snapshotPreCommit(6, 3); + assertEquals(1, replicatedLogImpl.size()); + assertEquals(6, replicatedLogImpl.getSnapshotIndex()); + + replicatedLogImpl.snapshotPreCommit(7, 3); + assertEquals(0, replicatedLogImpl.size()); + assertEquals(7, replicatedLogImpl.getSnapshotIndex()); + + //running it again on an empty list should not throw exception + replicatedLogImpl.snapshotPreCommit(7, 3); + assertEquals(0, replicatedLogImpl.size()); + assertEquals(7, replicatedLogImpl.getSnapshotIndex()); + + } + + @Test + public void testIsPresent() { + assertTrue(replicatedLogImpl.isPresent(0)); + assertTrue(replicatedLogImpl.isPresent(1)); + assertTrue(replicatedLogImpl.isPresent(2)); + assertTrue(replicatedLogImpl.isPresent(3)); + + replicatedLogImpl.append(new MockReplicatedLogEntry(2, 4, new MockPayload("D"))); + replicatedLogImpl.snapshotPreCommit(3, 2); //snapshot on 3 + replicatedLogImpl.snapshotCommit(); + + assertFalse(replicatedLogImpl.isPresent(0)); + assertFalse(replicatedLogImpl.isPresent(1)); + assertFalse(replicatedLogImpl.isPresent(2)); + assertFalse(replicatedLogImpl.isPresent(3)); + assertTrue(replicatedLogImpl.isPresent(4)); + + replicatedLogImpl.snapshotPreCommit(4, 2); //snapshot on 4 + replicatedLogImpl.snapshotCommit(); + assertFalse(replicatedLogImpl.isPresent(4)); + + replicatedLogImpl.append(new MockReplicatedLogEntry(2, 5, new MockPayload("D"))); + assertTrue(replicatedLogImpl.isPresent(5)); + } + // create a snapshot for test public Map takeSnapshot(final int numEntries) { Map map = new HashMap<>(numEntries); diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/FollowerLogInformationImplTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/FollowerLogInformationImplTest.java index a092c46533..84d1545a65 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/FollowerLogInformationImplTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/FollowerLogInformationImplTest.java @@ -55,8 +55,7 @@ public class FollowerLogInformationImplTest { // hence getting the actual elapsed time and do a match. // if the sleep has spilled over, then return the test gracefully private long sleepWithElaspsedTimeReturned(long millis) { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.start(); + Stopwatch stopwatch = Stopwatch.createStarted(); Uninterruptibles.sleepUninterruptibly(millis, TimeUnit.MILLISECONDS); stopwatch.stop(); return stopwatch.elapsed(TimeUnit.MILLISECONDS); diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActorContext.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActorContext.java index cd852eaae2..24581d6d2a 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActorContext.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/MockRaftActorContext.java @@ -12,8 +12,6 @@ import akka.actor.ActorRef; import akka.actor.ActorSelection; import akka.actor.ActorSystem; import akka.actor.Props; -import akka.event.Logging; -import akka.event.LoggingAdapter; import com.google.common.base.Preconditions; import com.google.protobuf.GeneratedMessage; import java.io.Serializable; @@ -22,6 +20,8 @@ import java.util.Map; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; import org.opendaylight.controller.protobuff.messages.cluster.raft.AppendEntriesMessages; import org.opendaylight.controller.protobuff.messages.cluster.raft.test.MockPayloadMessages; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class MockRaftActorContext implements RaftActorContext { @@ -34,6 +34,7 @@ public class MockRaftActorContext implements RaftActorContext { private ReplicatedLog replicatedLog; private Map peerAddresses = new HashMap<>(); private ConfigParams configParams; + private boolean snapshotCaptureInitiated; public MockRaftActorContext(){ electionTerm = null; @@ -52,7 +53,7 @@ public class MockRaftActorContext implements RaftActorContext { * Identifier of the actor whose election term information this is */ private final String id = id1; - private long currentTerm = 0; + private long currentTerm = 1; private String votedFor = ""; @Override @@ -87,7 +88,9 @@ public class MockRaftActorContext implements RaftActorContext { public void initReplicatedLog(){ this.replicatedLog = new SimpleReplicatedLog(); - this.replicatedLog.append(new MockReplicatedLogEntry(1, 1, new MockPayload(""))); + long term = getTermInformation().getCurrentTerm(); + this.replicatedLog.append(new MockReplicatedLogEntry(term, 0, new MockPayload("1"))); + this.replicatedLog.append(new MockReplicatedLogEntry(term, 1, new MockPayload("2"))); } @Override public ActorRef actorOf(Props props) { @@ -143,8 +146,8 @@ public class MockRaftActorContext implements RaftActorContext { return this.system; } - @Override public LoggingAdapter getLogger() { - return Logging.getLogger(system, this); + @Override public Logger getLogger() { + return LoggerFactory.getLogger(getClass()); } @Override public Map getPeerAddresses() { @@ -185,6 +188,16 @@ public class MockRaftActorContext implements RaftActorContext { return configParams; } + @Override + public void setSnapshotCaptureInitiated(boolean snapshotCaptureInitiated) { + this.snapshotCaptureInitiated = snapshotCaptureInitiated; + } + + @Override + public boolean isSnapshotCaptureInitiated() { + return snapshotCaptureInitiated; + } + public void setConfigParams(ConfigParams configParams) { this.configParams = configParams; } @@ -243,6 +256,36 @@ public class MockRaftActorContext implements RaftActorContext { public String toString() { return value; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MockPayload other = (MockPayload) obj; + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } } public static class MockReplicatedLogEntry implements ReplicatedLogEntry, Serializable { @@ -275,6 +318,52 @@ public class MockRaftActorContext implements RaftActorContext { public int size() { return getData().size(); } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + result = prime * result + (int) (index ^ (index >>> 32)); + result = prime * result + (int) (term ^ (term >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MockReplicatedLogEntry other = (MockReplicatedLogEntry) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + if (index != other.index) { + return false; + } + if (term != other.term) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("MockReplicatedLogEntry [term=").append(term).append(", index=").append(index) + .append(", data=").append(data).append("]"); + return builder.toString(); + } } public static class MockReplicatedLogBuilder { diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java index 6b266d710e..83868b6a2a 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/RaftActorTest.java @@ -1,6 +1,7 @@ package org.opendaylight.controller.cluster.raft; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -41,6 +42,7 @@ import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -48,6 +50,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.cluster.DataPersistenceProvider; import org.opendaylight.controller.cluster.datastore.DataPersistenceProviderMonitor; @@ -57,10 +60,13 @@ import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot; import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply; +import org.opendaylight.controller.cluster.raft.base.messages.SendHeartBeat; import org.opendaylight.controller.cluster.raft.behaviors.Follower; import org.opendaylight.controller.cluster.raft.behaviors.Leader; import org.opendaylight.controller.cluster.raft.client.messages.FindLeader; import org.opendaylight.controller.cluster.raft.client.messages.FindLeaderReply; +import org.opendaylight.controller.cluster.raft.messages.AppendEntries; +import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; import org.opendaylight.controller.cluster.raft.utils.MockAkkaJournal; @@ -72,9 +78,16 @@ import scala.concurrent.duration.FiniteDuration; public class RaftActorTest extends AbstractActorTest { + private TestActorFactory factory; + + @Before + public void setUp(){ + factory = new TestActorFactory(getSystem()); + } @After - public void tearDown() { + public void tearDown() throws Exception { + factory.close(); MockAkkaJournal.clearJournal(); MockSnapshotStore.setMockSnapshot(null); } @@ -86,6 +99,7 @@ public class RaftActorTest extends AbstractActorTest { private final CountDownLatch recoveryComplete = new CountDownLatch(1); private final List state; private ActorRef roleChangeNotifier; + private final CountDownLatch initializeBehaviorComplete = new CountDownLatch(1); public static final class MockRaftActorCreator implements Creator { private static final long serialVersionUID = 1L; @@ -114,7 +128,8 @@ public class RaftActorTest extends AbstractActorTest { } } - public MockRaftActor(String id, Map peerAddresses, Optional config, DataPersistenceProvider dataPersistenceProvider) { + public MockRaftActor(String id, Map peerAddresses, Optional config, + DataPersistenceProvider dataPersistenceProvider) { super(id, peerAddresses, config); state = new ArrayList<>(); this.delegate = mock(RaftActor.class); @@ -133,6 +148,14 @@ public class RaftActorTest extends AbstractActorTest { } } + public void waitForInitializeBehaviorComplete() { + try { + assertEquals("Behavior initialized", true, initializeBehaviorComplete.await(5, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + public List getState() { return state; } @@ -176,6 +199,12 @@ public class RaftActorTest extends AbstractActorTest { recoveryComplete.countDown(); } + @Override + protected void initializeBehavior() { + super.initializeBehavior(); + initializeBehaviorComplete.countDown(); + } + @Override protected void applyRecoverySnapshot(byte[] bytes) { delegate.applyRecoverySnapshot(bytes); @@ -316,7 +345,7 @@ public class RaftActorTest extends AbstractActorTest { @Test public void testRaftActorRecovery() throws Exception { new JavaTestKit(getSystem()) {{ - String persistenceId = "follower10"; + String persistenceId = factory.generateActorId("follower-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); // Set the heartbeat interval high to essentially disable election otherwise the test @@ -324,8 +353,8 @@ public class RaftActorTest extends AbstractActorTest { // log entry. config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - ActorRef followerActor = getSystem().actorOf(MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config)), persistenceId); + ActorRef followerActor = factory.createActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config)), persistenceId); watch(followerActor); @@ -337,15 +366,15 @@ public class RaftActorTest extends AbstractActorTest { int lastAppliedDuringSnapshotCapture = 3; int lastIndexDuringSnapshotCapture = 4; - // 4 messages as part of snapshot, which are applied to state - ByteString snapshotBytes = fromObject(Arrays.asList( - new MockRaftActorContext.MockPayload("A"), - new MockRaftActorContext.MockPayload("B"), - new MockRaftActorContext.MockPayload("C"), - new MockRaftActorContext.MockPayload("D"))); + // 4 messages as part of snapshot, which are applied to state + ByteString snapshotBytes = fromObject(Arrays.asList( + new MockRaftActorContext.MockPayload("A"), + new MockRaftActorContext.MockPayload("B"), + new MockRaftActorContext.MockPayload("C"), + new MockRaftActorContext.MockPayload("D"))); Snapshot snapshot = Snapshot.create(snapshotBytes.toByteArray(), - snapshotUnappliedEntries, lastIndexDuringSnapshotCapture, 1 , + snapshotUnappliedEntries, lastIndexDuringSnapshotCapture, 1, lastAppliedDuringSnapshotCapture, 1); MockSnapshotStore.setMockSnapshot(snapshot); MockSnapshotStore.setPersistenceId(persistenceId); @@ -378,8 +407,8 @@ public class RaftActorTest extends AbstractActorTest { unwatch(followerActor); //reinstate the actor - TestActorRef ref = TestActorRef.create(getSystem(), - MockRaftActor.props(persistenceId, Collections.emptyMap(), + TestActorRef ref = factory.createTestActor( + MockRaftActor.props(persistenceId, Collections.emptyMap(), Optional.of(config))); ref.underlyingActor().waitForRecoveryComplete(); @@ -405,28 +434,28 @@ public class RaftActorTest extends AbstractActorTest { public void testHandleRecoveryWhenDataPersistenceRecoveryApplicable() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testHandleRecoveryWhenDataPersistenceRecoveryApplicable"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config)), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config)), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); // Wait for akka's recovery to complete so it doesn't interfere. mockRaftActor.waitForRecoveryComplete(); - ByteString snapshotBytes = fromObject(Arrays.asList( + ByteString snapshotBytes = fromObject(Arrays.asList( new MockRaftActorContext.MockPayload("A"), new MockRaftActorContext.MockPayload("B"), new MockRaftActorContext.MockPayload("C"), new MockRaftActorContext.MockPayload("D"))); Snapshot snapshot = Snapshot.create(snapshotBytes.toByteArray(), - Lists.newArrayList(), 3, 1 ,3, 1); + Lists.newArrayList(), 3, 1, 3, 1); mockRaftActor.onReceiveRecover(new SnapshotOffer(new SnapshotMetadata(persistenceId, 100, 100), snapshot)); @@ -459,8 +488,6 @@ public class RaftActorTest extends AbstractActorTest { mockRaftActor.onReceiveRecover(mock(RecoveryCompleted.class)); - mockActorRef.tell(PoisonPill.getInstance(), getRef()); - }}; } @@ -474,28 +501,28 @@ public class RaftActorTest extends AbstractActorTest { public void testHandleRecoveryWhenDataPersistenceRecoveryNotApplicable() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testHandleRecoveryWhenDataPersistenceRecoveryNotApplicable"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), new DataPersistenceProviderMonitor()), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), new DataPersistenceProviderMonitor()), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); // Wait for akka's recovery to complete so it doesn't interfere. mockRaftActor.waitForRecoveryComplete(); - ByteString snapshotBytes = fromObject(Arrays.asList( + ByteString snapshotBytes = fromObject(Arrays.asList( new MockRaftActorContext.MockPayload("A"), new MockRaftActorContext.MockPayload("B"), new MockRaftActorContext.MockPayload("C"), new MockRaftActorContext.MockPayload("D"))); Snapshot snapshot = Snapshot.create(snapshotBytes.toByteArray(), - Lists.newArrayList(), 3, 1 ,3, 1); + Lists.newArrayList(), 3, 1, 3, 1); mockRaftActor.onReceiveRecover(new SnapshotOffer(new SnapshotMetadata(persistenceId, 100, 100), snapshot)); @@ -525,8 +552,6 @@ public class RaftActorTest extends AbstractActorTest { assertNotEquals("voted for", "foobar", mockRaftActor.getRaftActorContext().getTermInformation().getVotedFor()); mockRaftActor.onReceiveRecover(mock(RecoveryCompleted.class)); - - mockActorRef.tell(PoisonPill.getInstance(), getRef()); }}; } @@ -535,7 +560,7 @@ public class RaftActorTest extends AbstractActorTest { public void testUpdatingElectionTermCallsDataPersistence() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testUpdatingElectionTermCallsDataPersistence"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); @@ -545,17 +570,16 @@ public class RaftActorTest extends AbstractActorTest { DataPersistenceProviderMonitor dataPersistenceProviderMonitor = new DataPersistenceProviderMonitor(); dataPersistenceProviderMonitor.setPersistLatch(persistLatch); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProviderMonitor), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), dataPersistenceProviderMonitor), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); + mockRaftActor.waitForInitializeBehaviorComplete(); + mockRaftActor.getRaftActorContext().getTermInformation().updateAndPersist(10, "foobar"); assertEquals("Persist called", true, persistLatch.await(5, TimeUnit.SECONDS)); - - mockActorRef.tell(PoisonPill.getInstance(), getRef()); - } }; } @@ -564,7 +588,7 @@ public class RaftActorTest extends AbstractActorTest { public void testAddingReplicatedLogEntryCallsDataPersistence() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testAddingReplicatedLogEntryCallsDataPersistence"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); @@ -572,19 +596,18 @@ public class RaftActorTest extends AbstractActorTest { DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); + mockRaftActor.waitForInitializeBehaviorComplete(); + MockRaftActorContext.MockReplicatedLogEntry logEntry = new MockRaftActorContext.MockReplicatedLogEntry(10, 10, mock(Payload.class)); mockRaftActor.getRaftActorContext().getReplicatedLog().appendAndPersist(logEntry); verify(dataPersistenceProvider).persist(eq(logEntry), any(Procedure.class)); - - mockActorRef.tell(PoisonPill.getInstance(), getRef()); - } }; } @@ -593,7 +616,7 @@ public class RaftActorTest extends AbstractActorTest { public void testRemovingReplicatedLogEntryCallsDataPersistence() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testRemovingReplicatedLogEntryCallsDataPersistence"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); @@ -601,19 +624,18 @@ public class RaftActorTest extends AbstractActorTest { DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); + mockRaftActor.waitForInitializeBehaviorComplete(); + mockRaftActor.getReplicatedLog().appendAndPersist(new MockRaftActorContext.MockReplicatedLogEntry(1, 0, mock(Payload.class))); mockRaftActor.getRaftActorContext().getReplicatedLog().removeFromAndPersist(0); verify(dataPersistenceProvider, times(2)).persist(anyObject(), any(Procedure.class)); - - mockActorRef.tell(PoisonPill.getInstance(), getRef()); - } }; } @@ -622,7 +644,7 @@ public class RaftActorTest extends AbstractActorTest { public void testApplyLogEntriesCallsDataPersistence() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testApplyLogEntriesCallsDataPersistence"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); @@ -630,18 +652,19 @@ public class RaftActorTest extends AbstractActorTest { DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); + mockRaftActor.waitForInitializeBehaviorComplete(); + mockRaftActor.onReceiveCommand(new ApplyLogEntries(10)); verify(dataPersistenceProvider, times(1)).persist(anyObject(), any(Procedure.class)); - mockActorRef.tell(PoisonPill.getInstance(), getRef()); - } + }; } @@ -649,7 +672,7 @@ public class RaftActorTest extends AbstractActorTest { public void testCaptureSnapshotReplyCallsDataPersistence() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testCaptureSnapshotReplyCallsDataPersistence"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); @@ -657,19 +680,21 @@ public class RaftActorTest extends AbstractActorTest { DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), - MockRaftActor.props(persistenceId,Collections.emptyMap(), - Optional.of(config), dataPersistenceProvider), persistenceId); + TestActorRef mockActorRef = factory.createTestActor( + MockRaftActor.props(persistenceId, Collections.emptyMap(), + Optional.of(config), dataPersistenceProvider), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - ByteString snapshotBytes = fromObject(Arrays.asList( + mockRaftActor.waitForInitializeBehaviorComplete(); + + ByteString snapshotBytes = fromObject(Arrays.asList( new MockRaftActorContext.MockPayload("A"), new MockRaftActorContext.MockPayload("B"), new MockRaftActorContext.MockPayload("C"), new MockRaftActorContext.MockPayload("D"))); - mockRaftActor.onReceiveCommand(new CaptureSnapshot(-1,1,-1,1)); + mockRaftActor.onReceiveCommand(new CaptureSnapshot(-1, 1,-1, 1, -1, 1)); RaftActorContext raftActorContext = mockRaftActor.getRaftActorContext(); @@ -679,8 +704,6 @@ public class RaftActorTest extends AbstractActorTest { verify(dataPersistenceProvider).saveSnapshot(anyObject()); - mockActorRef.tell(PoisonPill.getInstance(), getRef()); - } }; } @@ -689,7 +712,7 @@ public class RaftActorTest extends AbstractActorTest { public void testSaveSnapshotSuccessCallsDataPersistence() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testSaveSnapshotSuccessCallsDataPersistence"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); @@ -697,16 +720,18 @@ public class RaftActorTest extends AbstractActorTest { DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1,0, mock(Payload.class))); - mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1,1, mock(Payload.class))); - mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1,2, mock(Payload.class))); - mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1,3, mock(Payload.class))); - mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1,4, mock(Payload.class))); + mockRaftActor.waitForInitializeBehaviorComplete(); + + mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1, 0, mock(Payload.class))); + mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1, 1, mock(Payload.class))); + mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1, 2, mock(Payload.class))); + mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1, 3, mock(Payload.class))); + mockRaftActor.getReplicatedLog().append(new MockRaftActorContext.MockReplicatedLogEntry(1, 4, mock(Payload.class))); ByteString snapshotBytes = fromObject(Arrays.asList( new MockRaftActorContext.MockPayload("A"), @@ -717,7 +742,8 @@ public class RaftActorTest extends AbstractActorTest { RaftActorContext raftActorContext = mockRaftActor.getRaftActorContext(); mockRaftActor.setCurrentBehavior(new Follower(raftActorContext)); - mockRaftActor.onReceiveCommand(new CaptureSnapshot(-1, 1, 2, 1)); + long replicatedToAllIndex = 1; + mockRaftActor.onReceiveCommand(new CaptureSnapshot(-1, 1, 2, 1, replicatedToAllIndex, 1)); verify(mockRaftActor.delegate).createSnapshot(); @@ -729,15 +755,16 @@ public class RaftActorTest extends AbstractActorTest { verify(dataPersistenceProvider).deleteMessages(100); - assertEquals(2, mockRaftActor.getReplicatedLog().size()); + assertEquals(3, mockRaftActor.getReplicatedLog().size()); + assertEquals(1, mockRaftActor.getCurrentBehavior().getReplicatedToAllIndex()); + assertNotNull(mockRaftActor.getReplicatedLog().get(2)); assertNotNull(mockRaftActor.getReplicatedLog().get(3)); assertNotNull(mockRaftActor.getReplicatedLog().get(4)); // Index 2 will not be in the log because it was removed due to snapshotting - assertNull(mockRaftActor.getReplicatedLog().get(2)); - - mockActorRef.tell(PoisonPill.getInstance(), getRef()); + assertNull(mockRaftActor.getReplicatedLog().get(1)); + assertNull(mockRaftActor.getReplicatedLog().get(0)); } }; @@ -748,7 +775,7 @@ public class RaftActorTest extends AbstractActorTest { new JavaTestKit(getSystem()) { { - String persistenceId = "testApplyState"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); @@ -756,11 +783,13 @@ public class RaftActorTest extends AbstractActorTest { DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), dataPersistenceProvider), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); + mockRaftActor.waitForInitializeBehaviorComplete(); + ReplicatedLogEntry entry = new MockRaftActorContext.MockReplicatedLogEntry(1, 5, new MockRaftActorContext.MockPayload("F")); @@ -768,8 +797,6 @@ public class RaftActorTest extends AbstractActorTest { verify(mockRaftActor.delegate).applyState(eq(mockActorRef), eq("apply-state"), anyObject()); - mockActorRef.tell(PoisonPill.getInstance(), getRef()); - } }; } @@ -778,7 +805,7 @@ public class RaftActorTest extends AbstractActorTest { public void testApplySnapshot() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testApplySnapshot"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); @@ -786,24 +813,26 @@ public class RaftActorTest extends AbstractActorTest { DataPersistenceProviderMonitor dataPersistenceProviderMonitor = new DataPersistenceProviderMonitor(); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProviderMonitor), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), dataPersistenceProviderMonitor), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); + mockRaftActor.waitForInitializeBehaviorComplete(); + ReplicatedLog oldReplicatedLog = mockRaftActor.getReplicatedLog(); - oldReplicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,0,mock(Payload.class))); - oldReplicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1,1,mock(Payload.class))); + oldReplicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, 0, mock(Payload.class))); + oldReplicatedLog.append(new MockRaftActorContext.MockReplicatedLogEntry(1, 1, mock(Payload.class))); oldReplicatedLog.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 2, - mock(Payload.class))); + new MockRaftActorContext.MockReplicatedLogEntry(1, 2, + mock(Payload.class))); ByteString snapshotBytes = fromObject(Arrays.asList( - new MockRaftActorContext.MockPayload("A"), - new MockRaftActorContext.MockPayload("B"), - new MockRaftActorContext.MockPayload("C"), - new MockRaftActorContext.MockPayload("D"))); + new MockRaftActorContext.MockPayload("A"), + new MockRaftActorContext.MockPayload("B"), + new MockRaftActorContext.MockPayload("C"), + new MockRaftActorContext.MockPayload("D"))); Snapshot snapshot = mock(Snapshot.class); @@ -816,15 +845,13 @@ public class RaftActorTest extends AbstractActorTest { verify(mockRaftActor.delegate).applySnapshot(eq(snapshot.getState())); assertTrue("The replicatedLog should have changed", - oldReplicatedLog != mockRaftActor.getReplicatedLog()); + oldReplicatedLog != mockRaftActor.getReplicatedLog()); assertEquals("lastApplied should be same as in the snapshot", - (Long) 3L, mockRaftActor.getLastApplied()); + (Long) 3L, mockRaftActor.getLastApplied()); assertEquals(0, mockRaftActor.getReplicatedLog().size()); - mockActorRef.tell(PoisonPill.getInstance(), getRef()); - } }; } @@ -833,7 +860,7 @@ public class RaftActorTest extends AbstractActorTest { public void testSaveSnapshotFailure() throws Exception { new JavaTestKit(getSystem()) { { - String persistenceId = "testSaveSnapshotFailure"; + String persistenceId = factory.generateActorId("leader-"); DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); @@ -841,12 +868,14 @@ public class RaftActorTest extends AbstractActorTest { DataPersistenceProviderMonitor dataPersistenceProviderMonitor = new DataPersistenceProviderMonitor(); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(persistenceId, - Collections.emptyMap(), Optional.of(config), dataPersistenceProviderMonitor), persistenceId); + TestActorRef mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), dataPersistenceProviderMonitor), persistenceId); MockRaftActor mockRaftActor = mockActorRef.underlyingActor(); - ByteString snapshotBytes = fromObject(Arrays.asList( + mockRaftActor.waitForInitializeBehaviorComplete(); + + ByteString snapshotBytes = fromObject(Arrays.asList( new MockRaftActorContext.MockPayload("A"), new MockRaftActorContext.MockPayload("B"), new MockRaftActorContext.MockPayload("C"), @@ -856,7 +885,7 @@ public class RaftActorTest extends AbstractActorTest { mockRaftActor.setCurrentBehavior(new Leader(raftActorContext)); - mockRaftActor.onReceiveCommand(new CaptureSnapshot(-1,1,-1,1)); + mockRaftActor.onReceiveCommand(new CaptureSnapshot(-1, 1, -1, 1, -1, 1)); mockRaftActor.onReceiveCommand(new CaptureSnapshotReply(snapshotBytes.toByteArray())); @@ -866,8 +895,6 @@ public class RaftActorTest extends AbstractActorTest { assertEquals("Snapshot index should not have advanced because save snapshot failed", -1, mockRaftActor.getReplicatedLog().getSnapshotIndex()); - mockActorRef.tell(PoisonPill.getInstance(), getRef()); - } }; } @@ -875,40 +902,331 @@ public class RaftActorTest extends AbstractActorTest { @Test public void testRaftRoleChangeNotifier() throws Exception { new JavaTestKit(getSystem()) {{ - ActorRef notifierActor = getSystem().actorOf(Props.create(MessageCollectorActor.class)); + ActorRef notifierActor = factory.createActor(Props.create(MessageCollectorActor.class)); + MessageCollectorActor.waitUntilReady(notifierActor); + DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); - String id = "testRaftRoleChangeNotifier"; + long heartBeatInterval = 100; + config.setHeartBeatInterval(FiniteDuration.create(heartBeatInterval, TimeUnit.MILLISECONDS)); + config.setElectionTimeoutFactor(1); - TestActorRef mockActorRef = TestActorRef.create(getSystem(), MockRaftActor.props(id, - Collections.emptyMap(), Optional.of(config), notifierActor), id); + String persistenceId = factory.generateActorId("notifier-"); - // sleeping for a minimum of 2 seconds, if it spans more its fine. - Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS); + factory.createTestActor(MockRaftActor.props(persistenceId, + Collections.emptyMap(), Optional.of(config), notifierActor), persistenceId); + + List matches = null; + for(int i = 0; i < 5000 / heartBeatInterval; i++) { + matches = MessageCollectorActor.getAllMatching(notifierActor, RoleChanged.class); + assertNotNull(matches); + if(matches.size() == 3) { + break; + } + Uninterruptibles.sleepUninterruptibly(heartBeatInterval, TimeUnit.MILLISECONDS); + } - List matches = MessageCollectorActor.getAllMatching(notifierActor, RoleChanged.class); - assertNotNull(matches); assertEquals(3, matches.size()); // check if the notifier got a role change from null to Follower - RoleChanged raftRoleChanged = (RoleChanged) matches.get(0); - assertEquals(id, raftRoleChanged.getMemberId()); + RoleChanged raftRoleChanged = matches.get(0); + assertEquals(persistenceId, raftRoleChanged.getMemberId()); assertNull(raftRoleChanged.getOldRole()); assertEquals(RaftState.Follower.name(), raftRoleChanged.getNewRole()); // check if the notifier got a role change from Follower to Candidate - raftRoleChanged = (RoleChanged) matches.get(1); - assertEquals(id, raftRoleChanged.getMemberId()); + raftRoleChanged = matches.get(1); + assertEquals(persistenceId, raftRoleChanged.getMemberId()); assertEquals(RaftState.Follower.name(), raftRoleChanged.getOldRole()); assertEquals(RaftState.Candidate.name(), raftRoleChanged.getNewRole()); // check if the notifier got a role change from Candidate to Leader - raftRoleChanged = (RoleChanged) matches.get(2); - assertEquals(id, raftRoleChanged.getMemberId()); + raftRoleChanged = matches.get(2); + assertEquals(persistenceId, raftRoleChanged.getMemberId()); assertEquals(RaftState.Candidate.name(), raftRoleChanged.getOldRole()); assertEquals(RaftState.Leader.name(), raftRoleChanged.getNewRole()); }}; } + @Test + public void testFakeSnapshotsForLeaderWithInRealSnapshots() throws Exception { + new JavaTestKit(getSystem()) { + { + String persistenceId = factory.generateActorId("leader-"); + String follower1Id = factory.generateActorId("follower-"); + + ActorRef followerActor1 = + factory.createActor(Props.create(MessageCollectorActor.class)); + + DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); + config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); + config.setIsolatedLeaderCheckInterval(new FiniteDuration(1, TimeUnit.DAYS)); + + DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); + + Map peerAddresses = new HashMap<>(); + peerAddresses.put(follower1Id, followerActor1.path().toString()); + + TestActorRef mockActorRef = factory.createTestActor( + MockRaftActor.props(persistenceId, peerAddresses, + Optional.of(config), dataPersistenceProvider), persistenceId); + + MockRaftActor leaderActor = mockActorRef.underlyingActor(); + + leaderActor.getRaftActorContext().setCommitIndex(4); + leaderActor.getRaftActorContext().setLastApplied(4); + leaderActor.getRaftActorContext().getTermInformation().update(1, persistenceId); + + leaderActor.waitForInitializeBehaviorComplete(); + + // create 8 entries in the log - 0 to 4 are applied and will get picked up as part of the capture snapshot + + Leader leader = new Leader(leaderActor.getRaftActorContext()); + leaderActor.setCurrentBehavior(leader); + assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state()); + + MockRaftActorContext.MockReplicatedLogBuilder logBuilder = new MockRaftActorContext.MockReplicatedLogBuilder(); + leaderActor.getRaftActorContext().setReplicatedLog(logBuilder.createEntries(0, 8, 1).build()); + + assertEquals(8, leaderActor.getReplicatedLog().size()); + + leaderActor.onReceiveCommand(new CaptureSnapshot(6, 1, 4, 1, 4, 1)); + + leaderActor.getRaftActorContext().setSnapshotCaptureInitiated(true); + verify(leaderActor.delegate).createSnapshot(); + + assertEquals(8, leaderActor.getReplicatedLog().size()); + + assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state()); + //fake snapshot on index 5 + leaderActor.onReceiveCommand(new AppendEntriesReply(follower1Id, 1, true, 5, 1)); + + assertEquals(8, leaderActor.getReplicatedLog().size()); + + //fake snapshot on index 6 + assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state()); + leaderActor.onReceiveCommand(new AppendEntriesReply(follower1Id, 1, true, 6, 1)); + assertEquals(8, leaderActor.getReplicatedLog().size()); + + assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state()); + + assertEquals(8, leaderActor.getReplicatedLog().size()); + + ByteString snapshotBytes = fromObject(Arrays.asList( + new MockRaftActorContext.MockPayload("foo-0"), + new MockRaftActorContext.MockPayload("foo-1"), + new MockRaftActorContext.MockPayload("foo-2"), + new MockRaftActorContext.MockPayload("foo-3"), + new MockRaftActorContext.MockPayload("foo-4"))); + leaderActor.onReceiveCommand(new CaptureSnapshotReply(snapshotBytes.toByteArray())); + assertFalse(leaderActor.getRaftActorContext().isSnapshotCaptureInitiated()); + + // capture snapshot reply should remove the snapshotted entries only + assertEquals(3, leaderActor.getReplicatedLog().size()); + assertEquals(7, leaderActor.getReplicatedLog().lastIndex()); + + // add another non-replicated entry + leaderActor.getReplicatedLog().append( + new ReplicatedLogImplEntry(8, 1, new MockRaftActorContext.MockPayload("foo-8"))); + + //fake snapshot on index 7, since lastApplied = 7 , we would keep the last applied + leaderActor.onReceiveCommand(new AppendEntriesReply(follower1Id, 1, true, 7, 1)); + assertEquals(2, leaderActor.getReplicatedLog().size()); + assertEquals(8, leaderActor.getReplicatedLog().lastIndex()); + + } + }; + } + + @Test + public void testFakeSnapshotsForFollowerWithInRealSnapshots() throws Exception { + new JavaTestKit(getSystem()) { + { + String persistenceId = factory.generateActorId("follower-"); + String leaderId = factory.generateActorId("leader-"); + + + ActorRef leaderActor1 = + factory.createActor(Props.create(MessageCollectorActor.class)); + + DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); + config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); + config.setIsolatedLeaderCheckInterval(new FiniteDuration(1, TimeUnit.DAYS)); + + DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); + + Map peerAddresses = new HashMap<>(); + peerAddresses.put(leaderId, leaderActor1.path().toString()); + + TestActorRef mockActorRef = factory.createTestActor( + MockRaftActor.props(persistenceId, peerAddresses, + Optional.of(config), dataPersistenceProvider), persistenceId); + + MockRaftActor followerActor = mockActorRef.underlyingActor(); + followerActor.getRaftActorContext().setCommitIndex(4); + followerActor.getRaftActorContext().setLastApplied(4); + followerActor.getRaftActorContext().getTermInformation().update(1, persistenceId); + + followerActor.waitForInitializeBehaviorComplete(); + + + Follower follower = new Follower(followerActor.getRaftActorContext()); + followerActor.setCurrentBehavior(follower); + assertEquals(RaftState.Follower, followerActor.getCurrentBehavior().state()); + + // create 6 entries in the log - 0 to 4 are applied and will get picked up as part of the capture snapshot + MockRaftActorContext.MockReplicatedLogBuilder logBuilder = new MockRaftActorContext.MockReplicatedLogBuilder(); + followerActor.getRaftActorContext().setReplicatedLog(logBuilder.createEntries(0, 6, 1).build()); + + // log has indices 0-5 + assertEquals(6, followerActor.getReplicatedLog().size()); + + //snapshot on 4 + followerActor.onReceiveCommand(new CaptureSnapshot(5, 1, 4, 1, 4, 1)); + + followerActor.getRaftActorContext().setSnapshotCaptureInitiated(true); + verify(followerActor.delegate).createSnapshot(); + + assertEquals(6, followerActor.getReplicatedLog().size()); + + //fake snapshot on index 6 + List entries = + Arrays.asList( + (ReplicatedLogEntry) new MockRaftActorContext.MockReplicatedLogEntry(1, 6, + new MockRaftActorContext.MockPayload("foo-6")) + ); + followerActor.onReceiveCommand(new AppendEntries(1, leaderId, 5, 1, entries, 5, 5)); + assertEquals(7, followerActor.getReplicatedLog().size()); + + //fake snapshot on index 7 + assertEquals(RaftState.Follower, followerActor.getCurrentBehavior().state()); + + entries = + Arrays.asList( + (ReplicatedLogEntry) new MockRaftActorContext.MockReplicatedLogEntry(1, 7, + new MockRaftActorContext.MockPayload("foo-7")) + ); + followerActor.onReceiveCommand(new AppendEntries(1, leaderId, 6, 1, entries, 6, 6)); + assertEquals(8, followerActor.getReplicatedLog().size()); + + assertEquals(RaftState.Follower, followerActor.getCurrentBehavior().state()); + + + ByteString snapshotBytes = fromObject(Arrays.asList( + new MockRaftActorContext.MockPayload("foo-0"), + new MockRaftActorContext.MockPayload("foo-1"), + new MockRaftActorContext.MockPayload("foo-2"), + new MockRaftActorContext.MockPayload("foo-3"), + new MockRaftActorContext.MockPayload("foo-4"))); + followerActor.onReceiveCommand(new CaptureSnapshotReply(snapshotBytes.toByteArray())); + assertFalse(followerActor.getRaftActorContext().isSnapshotCaptureInitiated()); + + // capture snapshot reply should remove the snapshotted entries only till replicatedToAllIndex + assertEquals(3, followerActor.getReplicatedLog().size()); //indexes 5,6,7 left in the log + assertEquals(7, followerActor.getReplicatedLog().lastIndex()); + + entries = + Arrays.asList( + (ReplicatedLogEntry) new MockRaftActorContext.MockReplicatedLogEntry(1, 8, + new MockRaftActorContext.MockPayload("foo-7")) + ); + // send an additional entry 8 with leaderCommit = 7 + followerActor.onReceiveCommand(new AppendEntries(1, leaderId, 7, 1, entries, 7, 7)); + + // 7 and 8, as lastapplied is 7 + assertEquals(2, followerActor.getReplicatedLog().size()); + + } + }; + } + + @Test + public void testFakeSnapshotsForLeaderWithInInitiateSnapshots() throws Exception { + new JavaTestKit(getSystem()) { + { + String persistenceId = factory.generateActorId("leader-"); + String follower1Id = factory.generateActorId("follower-"); + String follower2Id = factory.generateActorId("follower-"); + + ActorRef followerActor1 = + factory.createActor(Props.create(MessageCollectorActor.class), follower1Id); + ActorRef followerActor2 = + factory.createActor(Props.create(MessageCollectorActor.class), follower2Id); + + DefaultConfigParamsImpl config = new DefaultConfigParamsImpl(); + config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS)); + config.setIsolatedLeaderCheckInterval(new FiniteDuration(1, TimeUnit.DAYS)); + + DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class); + + Map peerAddresses = new HashMap<>(); + peerAddresses.put(follower1Id, followerActor1.path().toString()); + peerAddresses.put(follower2Id, followerActor2.path().toString()); + + TestActorRef mockActorRef = factory.createTestActor( + MockRaftActor.props(persistenceId, peerAddresses, + Optional.of(config), dataPersistenceProvider), persistenceId); + + MockRaftActor leaderActor = mockActorRef.underlyingActor(); + leaderActor.getRaftActorContext().setCommitIndex(9); + leaderActor.getRaftActorContext().setLastApplied(9); + leaderActor.getRaftActorContext().getTermInformation().update(1, persistenceId); + + leaderActor.waitForInitializeBehaviorComplete(); + + Leader leader = new Leader(leaderActor.getRaftActorContext()); + leaderActor.setCurrentBehavior(leader); + assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state()); + + // create 5 entries in the log + MockRaftActorContext.MockReplicatedLogBuilder logBuilder = new MockRaftActorContext.MockReplicatedLogBuilder(); + leaderActor.getRaftActorContext().setReplicatedLog(logBuilder.createEntries(5, 10, 1).build()); + + //set the snapshot index to 4 , 0 to 4 are snapshotted + leaderActor.getRaftActorContext().getReplicatedLog().setSnapshotIndex(4); + //setting replicatedToAllIndex = 9, for the log to clear + leader.setReplicatedToAllIndex(9); + assertEquals(5, leaderActor.getReplicatedLog().size()); + assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state()); + + leaderActor.onReceiveCommand(new AppendEntriesReply(follower1Id, 1, true, 9, 1)); + assertEquals(5, leaderActor.getReplicatedLog().size()); + assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state()); + + // set the 2nd follower nextIndex to 1 which has been snapshotted + leaderActor.onReceiveCommand(new AppendEntriesReply(follower2Id, 1, true, 0, 1)); + assertEquals(5, leaderActor.getReplicatedLog().size()); + assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state()); + + // simulate a real snapshot + leaderActor.onReceiveCommand(new SendHeartBeat()); + assertEquals(5, leaderActor.getReplicatedLog().size()); + assertEquals(String.format("expected to be Leader but was %s. Current Leader = %s ", + leaderActor.getCurrentBehavior().state(), leaderActor.getLeaderId()) + , RaftState.Leader, leaderActor.getCurrentBehavior().state()); + + + //reply from a slow follower does not initiate a fake snapshot + leaderActor.onReceiveCommand(new AppendEntriesReply(follower2Id, 1, true, 9, 1)); + assertEquals("Fake snapshot should not happen when Initiate is in progress", 5, leaderActor.getReplicatedLog().size()); + + ByteString snapshotBytes = fromObject(Arrays.asList( + new MockRaftActorContext.MockPayload("foo-0"), + new MockRaftActorContext.MockPayload("foo-1"), + new MockRaftActorContext.MockPayload("foo-2"), + new MockRaftActorContext.MockPayload("foo-3"), + new MockRaftActorContext.MockPayload("foo-4"))); + leaderActor.onReceiveCommand(new CaptureSnapshotReply(snapshotBytes.toByteArray())); + assertFalse(leaderActor.getRaftActorContext().isSnapshotCaptureInitiated()); + + assertEquals("Real snapshot didn't clear the log till replicatedToAllIndex", 0, leaderActor.getReplicatedLog().size()); + + //reply from a slow follower after should not raise errors + leaderActor.onReceiveCommand(new AppendEntriesReply(follower2Id, 1, true, 5, 1)); + assertEquals(0, leaderActor.getReplicatedLog().size()); + } + }; + } + private ByteString fromObject(Object snapshot) throws Exception { ByteArrayOutputStream b = null; ObjectOutputStream o = null; @@ -928,4 +1246,5 @@ public class RaftActorTest extends AbstractActorTest { } } } + } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/TestActorFactory.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/TestActorFactory.java new file mode 100644 index 0000000000..b47df13fed --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/TestActorFactory.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.cluster.raft; + +/* + * 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 + */ + +import akka.actor.Actor; +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.PoisonPill; +import akka.actor.Props; +import akka.testkit.JavaTestKit; +import akka.testkit.TestActorRef; +import java.util.LinkedList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * TestActorFactory provides methods to create both normal and test actors and to kill them when the factory is closed + * The ideal usage for TestActorFactory is with try with resources,
+ * For example
+ *
+ *     try (TestActorFactory factory = new TestActorFactory(getSystem())){
+ *         factory.createActor(props);
+ *         factory.createTestActor(props);
+ *         factory.generateActorId("leader-");
+ *     }
+ * 
+ */ +public class TestActorFactory implements AutoCloseable { + private final ActorSystem system; + List createdActors = new LinkedList<>(); + Logger LOG = LoggerFactory.getLogger(getClass()); + private static int actorCount = 1; + + public TestActorFactory(ActorSystem system){ + this.system = system; + } + + /** + * Create a normal actor with an auto-generated name + * + * @param props + * @return + */ + public ActorRef createActor(Props props){ + ActorRef actorRef = system.actorOf(props); + createdActors.add(actorRef); + return actorRef; + } + + /** + * Create a normal actor with the passed in name + * @param props + * @param actorId name of actor + * @return + */ + public ActorRef createActor(Props props, String actorId){ + ActorRef actorRef = system.actorOf(props, actorId); + createdActors.add(actorRef); + return actorRef; + } + + /** + * Create a test actor with the passed in name + * @param props + * @param actorId + * @param + * @return + */ + public TestActorRef createTestActor(Props props, String actorId){ + TestActorRef actorRef = TestActorRef.create(system, props, actorId); + createdActors.add(actorRef); + return actorRef; + } + + /** + * Create a test actor with an auto-generated name + * @param props + * @param + * @return + */ + public TestActorRef createTestActor(Props props){ + TestActorRef actorRef = TestActorRef.create(system, props); + createdActors.add(actorRef); + return actorRef; + } + + /** + * Generate a friendly but unique actor id/name + * @param prefix + * @return + */ + public String generateActorId(String prefix){ + return prefix + actorCount++; + } + + @Override + public void close() { + new JavaTestKit(system) {{ + for(ActorRef actor : createdActors) { + watch(actor); + LOG.info("Killing actor {}", actor); + actor.tell(PoisonPill.getInstance(), ActorRef.noSender()); + expectTerminated(duration("5 seconds"), actor); + } + }}; + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeaderTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeaderTest.java new file mode 100644 index 0000000000..dd3ed2347c --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeaderTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.cluster.raft.behaviors; + +import static org.junit.Assert.assertTrue; +import akka.actor.ActorRef; +import akka.testkit.JavaTestKit; +import akka.testkit.TestActorRef; +import com.google.common.util.concurrent.Uninterruptibles; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.junit.Test; +import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext; +import org.opendaylight.controller.cluster.raft.base.messages.SendHeartBeat; +import org.opendaylight.controller.cluster.raft.utils.ForwardMessageToBehaviorActor; +import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; +import scala.concurrent.duration.FiniteDuration; + +public abstract class AbstractLeaderTest extends AbstractRaftActorBehaviorTest{ + + /** + * When we removed scheduling of heartbeat in the AbstractLeader constructor we ended up with a situation where + * if no follower responded to an initial AppendEntries heartbeats would not be sent to it. This test verifies + * that regardless of whether followers respond or not we schedule heartbeats. + * + * @throws Exception + */ + @Test + public void testLeaderSchedulesHeartbeatsEvenWhenNoFollowersRespondToInitialAppendEntries() throws Exception { + logStart("testLeaderSchedulesHeartbeatsEvenWhenNoFollowersRespondToInitialAppendEntries"); + new JavaTestKit(getSystem()) {{ + String leaderActorId = actorFactory.generateActorId("leader"); + String follower1ActorId = actorFactory.generateActorId("follower"); + String follower2ActorId = actorFactory.generateActorId("follower"); + + TestActorRef leaderActor = + actorFactory.createTestActor(ForwardMessageToBehaviorActor.props(), leaderActorId); + ActorRef follower1Actor = actorFactory.createActor(MessageCollectorActor.props(), follower1ActorId); + ActorRef follower2Actor = actorFactory.createActor(MessageCollectorActor.props(), follower2ActorId); + + MockRaftActorContext leaderActorContext = + new MockRaftActorContext(leaderActorId, getSystem(), leaderActor); + + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + configParams.setHeartBeatInterval(new FiniteDuration(200, TimeUnit.MILLISECONDS)); + configParams.setIsolatedLeaderCheckInterval(new FiniteDuration(10, TimeUnit.SECONDS)); + + leaderActorContext.setConfigParams(configParams); + + leaderActorContext.setReplicatedLog( + new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(1,5,1).build()); + + Map peerAddresses = new HashMap<>(); + peerAddresses.put(follower1ActorId, + follower1Actor.path().toString()); + peerAddresses.put(follower2ActorId, + follower2Actor.path().toString()); + + + leaderActorContext.setPeerAddresses(peerAddresses); + + RaftActorBehavior leader = createBehavior(leaderActorContext); + + leaderActor.underlyingActor().setBehavior(leader); + + Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS); + + List allMessages = MessageCollectorActor.getAllMatching(leaderActor, SendHeartBeat.class); + + // Need more than 1 heartbeat to be delivered because we waited for 1 second with heartbeat interval 200ms + assertTrue(String.format("%s messages is less than expected", allMessages.size()), + allMessages.size() > 1); + + }}; + } + +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehaviorTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehaviorTest.java index 3893018008..f56755b447 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehaviorTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractRaftActorBehaviorTest.java @@ -1,32 +1,52 @@ package org.opendaylight.controller.cluster.raft.behaviors; +import static org.junit.Assert.assertEquals; import akka.actor.ActorRef; import akka.actor.Props; -import akka.testkit.JavaTestKit; +import akka.testkit.TestActorRef; +import com.google.protobuf.ByteString; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.junit.After; +import org.junit.Assert; import org.junit.Test; import org.opendaylight.controller.cluster.raft.AbstractActorTest; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; import org.opendaylight.controller.cluster.raft.RaftActorContext; +import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; import org.opendaylight.controller.cluster.raft.SerializationUtils; +import org.opendaylight.controller.cluster.raft.TestActorFactory; import org.opendaylight.controller.cluster.raft.messages.AppendEntries; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; import org.opendaylight.controller.cluster.raft.messages.RaftRPC; import org.opendaylight.controller.cluster.raft.messages.RequestVote; import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload; -import org.opendaylight.controller.cluster.raft.utils.DoNothingActor; +import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; +import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; +public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; + protected final TestActorFactory actorFactory = new TestActorFactory(getSystem()); -public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { + private final TestActorRef behaviorActor = actorFactory.createTestActor( + Props.create(MessageCollectorActor.class), actorFactory.generateActorId("behavior")); + + RaftActorBehavior behavior; - private final ActorRef behaviorActor = getSystem().actorOf(Props.create( - DoNothingActor.class)); + @After + public void tearDown() throws Exception { + if(behavior != null) { + behavior.close(); + } + + actorFactory.close(); + } /** * This test checks that when a new Raft RPC message is received with a newer @@ -36,22 +56,19 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { */ @Test public void testHandleRaftRPCWithNewerTerm() throws Exception { - new JavaTestKit(getSystem()) {{ + RaftActorContext actorContext = createActorContext(); - assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(), + assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor, createAppendEntriesWithNewerTerm()); - assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(), + assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor, createAppendEntriesReplyWithNewerTerm()); - assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(), + assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor, createRequestVoteWithNewerTerm()); - assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(), + assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor, createRequestVoteReplyWithNewerTerm()); - - - }}; } @@ -63,144 +80,95 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { * @throws Exception */ @Test - public void testHandleAppendEntriesSenderTermLessThanReceiverTerm() - throws Exception { - new JavaTestKit(getSystem()) {{ - - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); + public void testHandleAppendEntriesSenderTermLessThanReceiverTerm() throws Exception { + MockRaftActorContext context = createActorContext(); // First set the receivers term to a high number (1000) context.getTermInformation().update(1000, "test"); - AppendEntries appendEntries = - new AppendEntries(100, "leader-1", 0, 0, null, 101); + AppendEntries appendEntries = new AppendEntries(100, "leader-1", 0, 0, null, 101, -1); - RaftActorBehavior behavior = createBehavior(context); + behavior = createBehavior(context); // Send an unknown message so that the state of the RaftActor remains unchanged - RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); + RaftActorBehavior expected = behavior.handleMessage(behaviorActor, "unknown"); - RaftActorBehavior raftBehavior = - behavior.handleMessage(getRef(), appendEntries); + RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries); - assertEquals(expected, raftBehavior); + assertEquals("Raft state", expected.state(), raftBehavior.state()); // Also expect an AppendEntriesReply to be sent where success is false - final Boolean out = new ExpectMsg(duration("1 seconds"), - "AppendEntriesReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof AppendEntriesReply) { - AppendEntriesReply reply = (AppendEntriesReply) in; - return reply.isSuccess(); - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(false, out); - - - }}; - } + AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching( + behaviorActor, AppendEntriesReply.class); - @Test - public void testHandleAppendEntriesAddSameEntryToLog(){ - new JavaTestKit(getSystem()) { - { + assertEquals("isSuccess", false, reply.isSuccess()); + } - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); - // First set the receivers term to lower number - context.getTermInformation().update(2, "test"); + @Test + public void testHandleAppendEntriesAddSameEntryToLog() throws Exception { + MockRaftActorContext context = createActorContext(); - // Prepare the receivers log - MockRaftActorContext.SimpleReplicatedLog log = - new MockRaftActorContext.SimpleReplicatedLog(); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero"))); + context.getTermInformation().update(2, "test"); - context.setReplicatedLog(log); + // Prepare the receivers log + MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero"); + setLastLogEntry(context, 2, 0, payload); - List entries = new ArrayList<>(); - entries.add( - new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero"))); + List entries = new ArrayList<>(); + entries.add(new MockRaftActorContext.MockReplicatedLogEntry(2, 0, payload)); - AppendEntries appendEntries = - new AppendEntries(2, "leader-1", -1, 1, entries, 0); + AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1); - RaftActorBehavior behavior = createBehavior(context); + behavior = createBehavior(context); - if (AbstractRaftActorBehaviorTest.this instanceof CandidateTest) { - // Resetting the Candidates term to make sure it will match - // the term sent by AppendEntries. If this was not done then - // the test will fail because the Candidate will assume that - // the message was sent to it from a lower term peer and will - // thus respond with a failure - context.getTermInformation().update(2, "test"); - } + if (behavior instanceof Candidate) { + // Resetting the Candidates term to make sure it will match + // the term sent by AppendEntries. If this was not done then + // the test will fail because the Candidate will assume that + // the message was sent to it from a lower term peer and will + // thus respond with a failure + context.getTermInformation().update(2, "test"); + } - // Send an unknown message so that the state of the RaftActor remains unchanged - RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); + // Send an unknown message so that the state of the RaftActor remains unchanged + RaftActorBehavior expected = behavior.handleMessage(behaviorActor, "unknown"); - RaftActorBehavior raftBehavior = - behavior.handleMessage(getRef(), appendEntries); + RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries); - assertEquals(expected, raftBehavior); + assertEquals("Raft state", expected.state(), raftBehavior.state()); - assertEquals(1, log.size()); + assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size()); + handleAppendEntriesAddSameEntryToLogReply(behaviorActor); + } - }}; + protected void handleAppendEntriesAddSameEntryToLogReply(TestActorRef replyActor) + throws Exception { + AppendEntriesReply reply = MessageCollectorActor.getFirstMatching(replyActor, AppendEntriesReply.class); + Assert.assertNull("Expected no AppendEntriesReply", reply); } /** * This test verifies that when a RequestVote is received by the RaftActor - * with a term which is greater than the RaftActors' currentTerm and the - * senders' log is more upto date than the receiver that the receiver grants - * the vote to the sender + * with the senders' log is more up to date than the receiver that the receiver grants + * the vote to the sender. */ @Test - public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermAndSenderLogMoreUpToDate() { - new JavaTestKit(getSystem()) {{ - - new Within(duration("1 seconds")) { - protected void run() { - - RaftActorBehavior behavior = createBehavior( - createActorContext(behaviorActor)); - - RaftActorBehavior raftBehavior = behavior.handleMessage(getTestActor(), - new RequestVote(1000, "test", 10000, 999)); - - if(!(behavior instanceof Follower)){ - assertTrue(raftBehavior instanceof Follower); - } else { - - final Boolean out = - new ExpectMsg(duration("1 seconds"), - "RequestVoteReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof RequestVoteReply) { - RequestVoteReply reply = - (RequestVoteReply) in; - return reply.isVoteGranted(); - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(true, out); - } - } - }; - }}; + public void testHandleRequestVoteWhenSenderLogMoreUpToDate() { + MockRaftActorContext context = createActorContext(); + + behavior = createBehavior(context); + + context.getTermInformation().update(1, "test"); + + behavior.handleMessage(behaviorActor, new RequestVote(context.getTermInformation().getCurrentTerm(), + "test", 10000, 999)); + + RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor, + RequestVoteReply.class); + assertEquals("isVoteGranted", true, reply.isVoteGranted()); } /** @@ -209,51 +177,24 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { * log then the receiving RaftActor will not grant the vote to the sender */ @Test - public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermButSenderLogLessUptoDate() { - new JavaTestKit(getSystem()) {{ - - new Within(duration("1 seconds")) { - protected void run() { - - RaftActorContext actorContext = - createActorContext(behaviorActor); - - MockRaftActorContext.SimpleReplicatedLog - log = new MockRaftActorContext.SimpleReplicatedLog(); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(20000, - 1000000, new MockRaftActorContext.MockPayload(""))); - - ((MockRaftActorContext) actorContext).setReplicatedLog(log); - - RaftActorBehavior behavior = createBehavior(actorContext); - - RaftActorBehavior raftBehavior = behavior.handleMessage(getTestActor(), - new RequestVote(1000, "test", 10000, 999)); - - if(!(behavior instanceof Follower)){ - assertTrue(raftBehavior instanceof Follower); - } else { - final Boolean out = - new ExpectMsg(duration("1 seconds"), - "RequestVoteReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof RequestVoteReply) { - RequestVoteReply reply = - (RequestVoteReply) in; - return reply.isVoteGranted(); - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(false, out); - } - } - }; - }}; + public void testHandleRequestVoteWhenSenderLogLessUptoDate() { + MockRaftActorContext context = createActorContext(); + + behavior = createBehavior(context); + + context.getTermInformation().update(1, "test"); + + int index = 2000; + setLastLogEntry(context, context.getTermInformation().getCurrentTerm(), index, + new MockRaftActorContext.MockPayload("")); + + behavior.handleMessage(behaviorActor, new RequestVote( + context.getTermInformation().getCurrentTerm(), "test", + index - 1, context.getTermInformation().getCurrentTerm())); + + RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor, + RequestVoteReply.class); + assertEquals("isVoteGranted", false, reply.isVoteGranted()); } @@ -265,54 +206,81 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { */ @Test public void testHandleRequestVoteWhenSenderTermLessThanCurrentTerm() { - new JavaTestKit(getSystem()) {{ - - new Within(duration("1 seconds")) { - protected void run() { - - RaftActorContext context = - createActorContext(behaviorActor); - - context.getTermInformation().update(1000, null); - - RaftActorBehavior follower = createBehavior(context); - - follower.handleMessage(getTestActor(), - new RequestVote(999, "test", 10000, 999)); - - final Boolean out = - new ExpectMsg(duration("1 seconds"), - "RequestVoteReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof RequestVoteReply) { - RequestVoteReply reply = - (RequestVoteReply) in; - return reply.isVoteGranted(); - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(false, out); - } - }; - }}; + RaftActorContext context = createActorContext(); + + context.getTermInformation().update(1000, null); + + behavior = createBehavior(context); + + behavior.handleMessage(behaviorActor, new RequestVote(999, "test", 10000, 999)); + + RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor, + RequestVoteReply.class); + assertEquals("isVoteGranted", false, reply.isVoteGranted()); } - protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm( - ActorRef actorRef, RaftRPC rpc) { + @Test + public void testPerformSnapshot() { + MockRaftActorContext context = new MockRaftActorContext("test", getSystem(), behaviorActor); + AbstractRaftActorBehavior abstractBehavior = (AbstractRaftActorBehavior) createBehavior(context); + if (abstractBehavior instanceof Candidate) { + return; + } + + context.getTermInformation().update(1, "test"); + + //log has 1 entry with replicatedToAllIndex = 0, does not do anything, returns the + context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 1, 1).build()); + context.setLastApplied(0); + abstractBehavior.performSnapshotWithoutCapture(0); + assertEquals(-1, abstractBehavior.getReplicatedToAllIndex()); + assertEquals(1, context.getReplicatedLog().size()); + + //2 entries, lastApplied still 0, no purging. + context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build()); + context.setLastApplied(0); + abstractBehavior.performSnapshotWithoutCapture(0); + assertEquals(-1, abstractBehavior.getReplicatedToAllIndex()); + assertEquals(2, context.getReplicatedLog().size()); + + //2 entries, lastApplied still 0, no purging. + context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build()); + context.setLastApplied(1); + abstractBehavior.performSnapshotWithoutCapture(0); + assertEquals(0, abstractBehavior.getReplicatedToAllIndex()); + assertEquals(1, context.getReplicatedLog().size()); + + //5 entries, lastApplied =2 and replicatedIndex = 3, but since we want to keep the lastapplied, indices 0 and 1 will only get purged + context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 5, 1).build()); + context.setLastApplied(2); + abstractBehavior.performSnapshotWithoutCapture(3); + assertEquals(1, abstractBehavior.getReplicatedToAllIndex()); + assertEquals(3, context.getReplicatedLog().size()); + + // scenario where Last applied > Replicated to all index (becoz of a slow follower) + context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); + context.setLastApplied(2); + abstractBehavior.performSnapshotWithoutCapture(1); + assertEquals(1, abstractBehavior.getReplicatedToAllIndex()); + assertEquals(1, context.getReplicatedLog().size()); + } + + + protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(RaftActorContext actorContext, + ActorRef actorRef, RaftRPC rpc) throws Exception { - RaftActorContext actorContext = createActorContext(); Payload p = new MockRaftActorContext.MockPayload(""); - setLastLogEntry( - (MockRaftActorContext) actorContext, 0, 0, p); + setLastLogEntry((MockRaftActorContext) actorContext, 1, 0, p); + actorContext.getTermInformation().update(1, "test"); + + RaftActorBehavior origBehavior = createBehavior(actorContext); + RaftActorBehavior raftBehavior = origBehavior.handleMessage(actorRef, rpc); - RaftActorBehavior raftBehavior = createBehavior(actorContext) - .handleMessage(actorRef, rpc); + assertEquals("New raft state", RaftState.Follower, raftBehavior.state()); + assertEquals("New election term", rpc.getTerm(), actorContext.getTermInformation().getCurrentTerm()); - assertTrue(raftBehavior instanceof Follower); + origBehavior.close(); + raftBehavior.close(); } protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry( @@ -321,10 +289,9 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { new MockRaftActorContext.MockReplicatedLogEntry(term, index, data)); } - protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry( - MockRaftActorContext actorContext, ReplicatedLogEntry logEntry) { - MockRaftActorContext.SimpleReplicatedLog - log = new MockRaftActorContext.SimpleReplicatedLog(); + protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(MockRaftActorContext actorContext, + ReplicatedLogEntry logEntry) { + MockRaftActorContext.SimpleReplicatedLog log = new MockRaftActorContext.SimpleReplicatedLog(); log.append(logEntry); actorContext.setReplicatedLog(log); @@ -338,16 +305,16 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { return createBehavior(createActorContext()); } - protected RaftActorContext createActorContext() { + protected MockRaftActorContext createActorContext() { return new MockRaftActorContext(); } - protected RaftActorContext createActorContext(ActorRef actor) { + protected MockRaftActorContext createActorContext(ActorRef actor) { return new MockRaftActorContext("test", getSystem(), actor); } protected AppendEntries createAppendEntriesWithNewerTerm() { - return new AppendEntries(100, "leader-1", 0, 0, null, 1); + return new AppendEntries(100, "leader-1", 0, 0, null, 1, -1); } protected AppendEntriesReply createAppendEntriesReplyWithNewerTerm() { @@ -365,4 +332,18 @@ public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest { protected Object fromSerializableMessage(Object serializable){ return SerializationUtils.fromSerializable(serializable); } + + protected ByteString toByteString(Map state) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try(ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(state); + return ByteString.copyFrom(bos.toByteArray()); + } catch (IOException e) { + throw new AssertionError("IOException occurred converting Map to Bytestring", e); + } + } + + protected void logStart(String name) { + LoggerFactory.getLogger(LeaderTest.class).info("Starting " + name); + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/CandidateTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/CandidateTest.java index 485ee4b316..60f45523cf 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/CandidateTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/CandidateTest.java @@ -1,67 +1,48 @@ package org.opendaylight.controller.cluster.raft.behaviors; +import static org.junit.Assert.assertEquals; import akka.actor.ActorRef; import akka.actor.Props; -import akka.testkit.JavaTestKit; -import org.junit.Assert; +import akka.testkit.TestActorRef; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; import org.opendaylight.controller.cluster.raft.RaftActorContext; +import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout; import org.opendaylight.controller.cluster.raft.messages.AppendEntries; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; +import org.opendaylight.controller.cluster.raft.messages.RaftRPC; import org.opendaylight.controller.cluster.raft.messages.RequestVote; import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; -import org.opendaylight.controller.cluster.raft.utils.DoNothingActor; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import static org.junit.Assert.assertEquals; +import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; public class CandidateTest extends AbstractRaftActorBehaviorTest { - private final ActorRef candidateActor = getSystem().actorOf(Props.create( - DoNothingActor.class)); + private final TestActorRef candidateActor = actorFactory.createTestActor( + Props.create(MessageCollectorActor.class), actorFactory.generateActorId("candidate")); - private final ActorRef peerActor1 = getSystem().actorOf(Props.create( - DoNothingActor.class)); + private TestActorRef[] peerActors; - private final ActorRef peerActor2 = getSystem().actorOf(Props.create( - DoNothingActor.class)); - - private final ActorRef peerActor3 = getSystem().actorOf(Props.create( - DoNothingActor.class)); - - private final ActorRef peerActor4 = getSystem().actorOf(Props.create( - DoNothingActor.class)); - - private final Map onePeer = new HashMap<>(); - private final Map twoPeers = new HashMap<>(); - private final Map fourPeers = new HashMap<>(); + private RaftActorBehavior candidate; @Before public void setUp(){ - onePeer.put(peerActor1.path().toString(), - peerActor1.path().toString()); - - twoPeers.put(peerActor1.path().toString(), - peerActor1.path().toString()); - twoPeers.put(peerActor2.path().toString(), - peerActor2.path().toString()); - - fourPeers.put(peerActor1.path().toString(), - peerActor1.path().toString()); - fourPeers.put(peerActor2.path().toString(), - peerActor2.path().toString()); - fourPeers.put(peerActor3.path().toString(), - peerActor3.path().toString()); - fourPeers.put(peerActor4.path().toString(), - peerActor3.path().toString()); + } + @Override + @After + public void tearDown() throws Exception { + if(candidate != null) { + candidate.close(); + } + super.tearDown(); } @Test @@ -69,230 +50,167 @@ public class CandidateTest extends AbstractRaftActorBehaviorTest { RaftActorContext raftActorContext = createActorContext(); long expectedTerm = raftActorContext.getTermInformation().getCurrentTerm(); - new Candidate(raftActorContext); + candidate = new Candidate(raftActorContext); - assertEquals(expectedTerm+1, raftActorContext.getTermInformation().getCurrentTerm()); - assertEquals(raftActorContext.getId(), raftActorContext.getTermInformation().getVotedFor()); + assertEquals("getCurrentTerm", expectedTerm+1, raftActorContext.getTermInformation().getCurrentTerm()); + assertEquals("getVotedFor", raftActorContext.getId(), raftActorContext.getTermInformation().getVotedFor()); } @Test public void testThatAnElectionTimeoutIsTriggered(){ - new JavaTestKit(getSystem()) {{ - - new Within(DefaultConfigParamsImpl.HEART_BEAT_INTERVAL.$times(6)) { - protected void run() { - - Candidate candidate = new Candidate(createActorContext(getTestActor())); - - final Boolean out = new ExpectMsg(DefaultConfigParamsImpl.HEART_BEAT_INTERVAL.$times(6), "ElectionTimeout") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof ElectionTimeout) { - return true; - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(true, out); - } - }; - }}; + MockRaftActorContext actorContext = createActorContext(); + candidate = new Candidate(actorContext); + + MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class, + actorContext.getConfigParams().getElectionTimeOutInterval().$times(6).toMillis()); } @Test public void testHandleElectionTimeoutWhenThereAreZeroPeers(){ RaftActorContext raftActorContext = createActorContext(); - Candidate candidate = - new Candidate(raftActorContext); + candidate = new Candidate(raftActorContext); - RaftActorBehavior raftBehavior = + RaftActorBehavior newBehavior = candidate.handleMessage(candidateActor, new ElectionTimeout()); - Assert.assertTrue(raftBehavior instanceof Leader); + assertEquals("Behavior", RaftState.Leader, newBehavior.state()); } @Test - public void testHandleElectionTimeoutWhenThereAreTwoNodesInCluster(){ - MockRaftActorContext raftActorContext = - (MockRaftActorContext) createActorContext(); - raftActorContext.setPeerAddresses(onePeer); - Candidate candidate = - new Candidate(raftActorContext); - - RaftActorBehavior raftBehavior = - candidate.handleMessage(candidateActor, new ElectionTimeout()); + public void testHandleElectionTimeoutWhenThereAreTwoNodeCluster(){ + MockRaftActorContext raftActorContext = createActorContext(); + raftActorContext.setPeerAddresses(setupPeers(1)); + candidate = new Candidate(raftActorContext); - Assert.assertTrue(raftBehavior instanceof Candidate); + candidate = candidate.handleMessage(candidateActor, new ElectionTimeout()); + + assertEquals("Behavior", RaftState.Candidate, candidate.state()); } @Test - public void testBecomeLeaderOnReceivingMajorityVotesInThreeNodesInCluster(){ - MockRaftActorContext raftActorContext = - (MockRaftActorContext) createActorContext(); - raftActorContext.setPeerAddresses(twoPeers); - Candidate candidate = - new Candidate(raftActorContext); - - RaftActorBehavior behaviorOnFirstVote = candidate.handleMessage(peerActor1, new RequestVoteReply(0, true)); + public void testBecomeLeaderOnReceivingMajorityVotesInThreeNodeCluster(){ + MockRaftActorContext raftActorContext = createActorContext(); + raftActorContext.setPeerAddresses(setupPeers(2)); + candidate = new Candidate(raftActorContext); - Assert.assertTrue(behaviorOnFirstVote instanceof Leader); + candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true)); + assertEquals("Behavior", RaftState.Leader, candidate.state()); } @Test - public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodesInCluster(){ - MockRaftActorContext raftActorContext = - (MockRaftActorContext) createActorContext(); - raftActorContext.setPeerAddresses(fourPeers); - Candidate candidate = - new Candidate(raftActorContext); + public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodeCluster(){ + MockRaftActorContext raftActorContext = createActorContext(); + raftActorContext.setPeerAddresses(setupPeers(4)); + candidate = new Candidate(raftActorContext); - RaftActorBehavior behaviorOnFirstVote = candidate.handleMessage(peerActor1, new RequestVoteReply(0, true)); + // First peers denies the vote. + candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, false)); - RaftActorBehavior behaviorOnSecondVote = candidate.handleMessage(peerActor2, new RequestVoteReply(0, true)); + assertEquals("Behavior", RaftState.Candidate, candidate.state()); - Assert.assertTrue(behaviorOnFirstVote instanceof Candidate); - Assert.assertTrue(behaviorOnSecondVote instanceof Leader); + candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, true)); - } + assertEquals("Behavior", RaftState.Candidate, candidate.state()); - @Test - public void testResponseToAppendEntriesWithLowerTerm(){ - new JavaTestKit(getSystem()) {{ - - new Within(duration("1 seconds")) { - protected void run() { - - Candidate candidate = new Candidate(createActorContext(getTestActor())); - - candidate.handleMessage(getTestActor(), new AppendEntries(0, "test", 0,0,Collections.emptyList(), 0)); - - final Boolean out = new ExpectMsg(duration("1 seconds"), "AppendEntriesResponse") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof AppendEntriesReply) { - AppendEntriesReply reply = (AppendEntriesReply) in; - return reply.isSuccess(); - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(false, out); - } - }; - }}; + candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true)); + + assertEquals("Behavior", RaftState.Leader, candidate.state()); } @Test - public void testResponseToRequestVoteWithLowerTerm(){ - new JavaTestKit(getSystem()) {{ - - new Within(duration("1 seconds")) { - protected void run() { - - Candidate candidate = new Candidate(createActorContext(getTestActor())); - - candidate.handleMessage(getTestActor(), new RequestVote(0, "test", 0, 0)); - - final Boolean out = new ExpectMsg(duration("1 seconds"), "AppendEntriesResponse") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof RequestVoteReply) { - RequestVoteReply reply = (RequestVoteReply) in; - return reply.isVoteGranted(); - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(false, out); - } - }; - }}; + public void testResponseToHandleAppendEntriesWithLowerTerm() { + candidate = new Candidate(createActorContext()); + + setupPeers(1); + candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0, + Collections.emptyList(), 0, -1)); + + AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching( + peerActors[0], AppendEntriesReply.class); + assertEquals("isSuccess", false, reply.isSuccess()); + assertEquals("getTerm", 2, reply.getTerm()); } @Test - public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNull(){ - new JavaTestKit(getSystem()) {{ - - new Within(duration("1 seconds")) { - protected void run() { - - RaftActorContext context = createActorContext(getTestActor()); - - context.getTermInformation().update(1000, null); - - // Once a candidate is created it will immediately increment the current term so after - // construction the currentTerm should be 1001 - RaftActorBehavior follower = createBehavior(context); - - follower.handleMessage(getTestActor(), new RequestVote(1001, "test", 10000, 999)); - - final Boolean out = new ExpectMsg(duration("1 seconds"), "RequestVoteReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof RequestVoteReply) { - RequestVoteReply reply = (RequestVoteReply) in; - return reply.isVoteGranted(); - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(true, out); - } - }; - }}; + public void testResponseToRequestVoteWithLowerTerm() { + candidate = new Candidate(createActorContext()); + + setupPeers(1); + candidate.handleMessage(peerActors[0], new RequestVote(1, "test", 0, 0)); + + RequestVoteReply reply = MessageCollectorActor.expectFirstMatching( + peerActors[0], RequestVoteReply.class); + assertEquals("isVoteGranted", false, reply.isVoteGranted()); + assertEquals("getTerm", 2, reply.getTerm()); } @Test - public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNotTheSameAsCandidateId(){ - new JavaTestKit(getSystem()) {{ + public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForMatches() { + MockRaftActorContext context = createActorContext(); + context.getTermInformation().update(1000, null); + + // Once a candidate is created it will immediately increment the current term so after + // construction the currentTerm should be 1001 + candidate = new Candidate(context); - new Within(duration("1 seconds")) { - protected void run() { + setupPeers(1); + candidate.handleMessage(peerActors[0], new RequestVote(1001, context.getId(), 10000, 999)); - RaftActorContext context = createActorContext(getTestActor()); + RequestVoteReply reply = MessageCollectorActor.expectFirstMatching( + peerActors[0], RequestVoteReply.class); + assertEquals("isVoteGranted", true, reply.isVoteGranted()); + assertEquals("getTerm", 1001, reply.getTerm()); + } - context.getTermInformation().update(1000, "test"); + @Test + public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForDoesNotMatch() { + MockRaftActorContext context = createActorContext(); + context.getTermInformation().update(1000, null); - RaftActorBehavior follower = createBehavior(context); + // Once a candidate is created it will immediately increment the current term so after + // construction the currentTerm should be 1001 + candidate = new Candidate(context); - follower.handleMessage(getTestActor(), new RequestVote(1001, "candidate", 10000, 999)); + setupPeers(1); - final Boolean out = new ExpectMsg(duration("1 seconds"), "RequestVoteReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof RequestVoteReply) { - RequestVoteReply reply = (RequestVoteReply) in; - return reply.isVoteGranted(); - } else { - throw noMatch(); - } - } - }.get(); + // RequestVote candidate ID ("candidate2") does not match this candidate's votedFor + // (it votes for itself) + candidate.handleMessage(peerActors[0], new RequestVote(1001, "candidate2", 10000, 999)); - assertEquals(false, out); - } - }; - }}; + RequestVoteReply reply = MessageCollectorActor.expectFirstMatching( + peerActors[0], RequestVoteReply.class); + assertEquals("isVoteGranted", false, reply.isVoteGranted()); + assertEquals("getTerm", 1001, reply.getTerm()); } - @Override protected RaftActorBehavior createBehavior(RaftActorContext actorContext) { + @Override + protected RaftActorBehavior createBehavior(RaftActorContext actorContext) { return new Candidate(actorContext); } - @Override protected RaftActorContext createActorContext() { - return new MockRaftActorContext("test", getSystem(), candidateActor); + @Override protected MockRaftActorContext createActorContext() { + return new MockRaftActorContext("candidate", getSystem(), candidateActor); } + private Map setupPeers(int count) { + Map peerMap = new HashMap<>(); + peerActors = new TestActorRef[count]; + for(int i = 0; i < count; i++) { + peerActors[i] = actorFactory.createTestActor(Props.create(MessageCollectorActor.class), + actorFactory.generateActorId("peer")); + peerMap.put("peer" + (i+1), peerActors[i].path().toString()); + } + return peerMap; + } + + @Override + protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(RaftActorContext actorContext, + ActorRef actorRef, RaftRPC rpc) throws Exception { + super.assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, actorRef, rpc); + assertEquals("New votedFor", null, actorContext.getTermInformation().getVotedFor()); + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java index a04d6aeb55..4e8e7fe11b 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java @@ -1,156 +1,121 @@ package org.opendaylight.controller.cluster.raft.behaviors; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import akka.actor.ActorRef; import akka.actor.Props; -import akka.testkit.JavaTestKit; +import akka.testkit.TestActorRef; import com.google.protobuf.ByteString; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.Map; +import org.junit.After; +import org.junit.Assert; import org.junit.Test; -import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; import org.opendaylight.controller.cluster.raft.RaftActorContext; import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; +import org.opendaylight.controller.cluster.raft.Snapshot; import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot; import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout; import org.opendaylight.controller.cluster.raft.messages.AppendEntries; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; import org.opendaylight.controller.cluster.raft.messages.InstallSnapshot; import org.opendaylight.controller.cluster.raft.messages.InstallSnapshotReply; +import org.opendaylight.controller.cluster.raft.messages.RaftRPC; import org.opendaylight.controller.cluster.raft.messages.RequestVote; import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; -import org.opendaylight.controller.cluster.raft.utils.DoNothingActor; import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; public class FollowerTest extends AbstractRaftActorBehaviorTest { - private final ActorRef followerActor = getSystem().actorOf(Props.create( - DoNothingActor.class)); + private final TestActorRef followerActor = actorFactory.createTestActor( + Props.create(MessageCollectorActor.class), actorFactory.generateActorId("follower")); + private final TestActorRef leaderActor = actorFactory.createTestActor( + Props.create(MessageCollectorActor.class), actorFactory.generateActorId("leader")); - @Override protected RaftActorBehavior createBehavior(RaftActorContext actorContext) { + private RaftActorBehavior follower; + + @Override + @After + public void tearDown() throws Exception { + if(follower != null) { + follower.close(); + } + + super.tearDown(); + } + + @Override + protected RaftActorBehavior createBehavior(RaftActorContext actorContext) { return new Follower(actorContext); } - @Override protected RaftActorContext createActorContext() { + @Override + protected MockRaftActorContext createActorContext() { return createActorContext(followerActor); } - protected RaftActorContext createActorContext(ActorRef actorRef){ - return new MockRaftActorContext("test", getSystem(), actorRef); + @Override + protected MockRaftActorContext createActorContext(ActorRef actorRef){ + return new MockRaftActorContext("follower", getSystem(), actorRef); } @Test public void testThatAnElectionTimeoutIsTriggered(){ - new JavaTestKit(getSystem()) {{ - - new Within(DefaultConfigParamsImpl.HEART_BEAT_INTERVAL.$times(6)) { - protected void run() { - - Follower follower = new Follower(createActorContext(getTestActor())); - - final Boolean out = new ExpectMsg(DefaultConfigParamsImpl.HEART_BEAT_INTERVAL.$times(6), "ElectionTimeout") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof ElectionTimeout) { - return true; - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(true, out); - } - }; - }}; + MockRaftActorContext actorContext = createActorContext(); + follower = new Follower(actorContext); + + MessageCollectorActor.expectFirstMatching(followerActor, ElectionTimeout.class, + actorContext.getConfigParams().getElectionTimeOutInterval().$times(6).toMillis()); } @Test public void testHandleElectionTimeout(){ - RaftActorContext raftActorContext = createActorContext(); - Follower follower = - new Follower(raftActorContext); + logStart("testHandleElectionTimeout"); - RaftActorBehavior raftBehavior = - follower.handleMessage(followerActor, new ElectionTimeout()); + follower = new Follower(createActorContext()); + + RaftActorBehavior raftBehavior = follower.handleMessage(followerActor, new ElectionTimeout()); assertTrue(raftBehavior instanceof Candidate); } @Test public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNull(){ - new JavaTestKit(getSystem()) {{ - - new Within(duration("1 seconds")) { - protected void run() { - - RaftActorContext context = createActorContext(getTestActor()); + logStart("testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNull"); - context.getTermInformation().update(1000, null); + RaftActorContext context = createActorContext(); + long term = 1000; + context.getTermInformation().update(term, null); - RaftActorBehavior follower = createBehavior(context); + follower = createBehavior(context); - follower.handleMessage(getTestActor(), new RequestVote(1000, "test", 10000, 999)); + follower.handleMessage(leaderActor, new RequestVote(term, "test", 10000, 999)); - final Boolean out = new ExpectMsg(duration("1 seconds"), "RequestVoteReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof RequestVoteReply) { - RequestVoteReply reply = (RequestVoteReply) in; - return reply.isVoteGranted(); - } else { - throw noMatch(); - } - } - }.get(); + RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(leaderActor, RequestVoteReply.class); - assertEquals(true, out); - } - }; - }}; + assertEquals("isVoteGranted", true, reply.isVoteGranted()); + assertEquals("getTerm", term, reply.getTerm()); } @Test public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNotTheSameAsCandidateId(){ - new JavaTestKit(getSystem()) {{ - - new Within(duration("1 seconds")) { - protected void run() { - - RaftActorContext context = createActorContext(getTestActor()); + logStart("testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNotTheSameAsCandidateId"); - context.getTermInformation().update(1000, "test"); + RaftActorContext context = createActorContext(); + long term = 1000; + context.getTermInformation().update(term, "test"); - RaftActorBehavior follower = createBehavior(context); + follower = createBehavior(context); - follower.handleMessage(getTestActor(), new RequestVote(1000, "candidate", 10000, 999)); + follower.handleMessage(leaderActor, new RequestVote(term, "candidate", 10000, 999)); - final Boolean out = new ExpectMsg(duration("1 seconds"), "RequestVoteReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof RequestVoteReply) { - RequestVoteReply reply = (RequestVoteReply) in; - return reply.isVoteGranted(); - } else { - throw noMatch(); - } - } - }.get(); + RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(leaderActor, RequestVoteReply.class); - assertEquals(false, out); - } - }; - }}; + assertEquals("isVoteGranted", false, reply.isVoteGranted()); } /** @@ -163,32 +128,25 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { */ @Test public void testHandleAppendEntriesWithNewerCommitIndex() throws Exception { - new JavaTestKit(getSystem()) {{ + logStart("testHandleAppendEntriesWithNewerCommitIndex"); - RaftActorContext context = - createActorContext(); + MockRaftActorContext context = createActorContext(); - context.setLastApplied(100); - setLastLogEntry((MockRaftActorContext) context, 1, 100, + context.setLastApplied(100); + setLastLogEntry(context, 1, 100, new MockRaftActorContext.MockPayload("")); - ((MockRaftActorContext) context).getReplicatedLog().setSnapshotIndex(99); + context.getReplicatedLog().setSnapshotIndex(99); - List entries = - Arrays.asList( - (ReplicatedLogEntry) new MockRaftActorContext.MockReplicatedLogEntry(2, 101, - new MockRaftActorContext.MockPayload("foo")) - ); + List entries = Arrays.asList( + newReplicatedLogEntry(2, 101, "foo")); - // The new commitIndex is 101 - AppendEntries appendEntries = - new AppendEntries(2, "leader-1", 100, 1, entries, 101); + // The new commitIndex is 101 + AppendEntries appendEntries = new AppendEntries(2, "leader-1", 100, 1, entries, 101, 100); - RaftActorBehavior raftBehavior = - createBehavior(context).handleMessage(getRef(), appendEntries); + follower = createBehavior(context); + follower.handleMessage(leaderActor, appendEntries); - assertEquals(101L, context.getLastApplied()); - - }}; + assertEquals("getLastApplied", 101L, context.getLastApplied()); } /** @@ -199,58 +157,30 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { * @throws Exception */ @Test - public void testHandleAppendEntriesSenderPrevLogTermNotSameAsReceiverPrevLogTerm() - throws Exception { - new JavaTestKit(getSystem()) {{ - - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); - - // First set the receivers term to lower number - context.getTermInformation().update(95, "test"); - - // Set the last log entry term for the receiver to be greater than - // what we will be sending as the prevLogTerm in AppendEntries - MockRaftActorContext.SimpleReplicatedLog mockReplicatedLog = - setLastLogEntry(context, 20, 0, new MockRaftActorContext.MockPayload("")); - - // AppendEntries is now sent with a bigger term - // this will set the receivers term to be the same as the sender's term - AppendEntries appendEntries = - new AppendEntries(100, "leader-1", 0, 0, null, 101); + public void testHandleAppendEntriesSenderPrevLogTermNotSameAsReceiverPrevLogTerm() { + logStart("testHandleAppendEntriesSenderPrevLogTermNotSameAsReceiverPrevLogTerm"); - RaftActorBehavior behavior = createBehavior(context); + MockRaftActorContext context = createActorContext(); - // Send an unknown message so that the state of the RaftActor remains unchanged - RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); + // First set the receivers term to lower number + context.getTermInformation().update(95, "test"); - RaftActorBehavior raftBehavior = - behavior.handleMessage(getRef(), appendEntries); + // AppendEntries is now sent with a bigger term + // this will set the receivers term to be the same as the sender's term + AppendEntries appendEntries = new AppendEntries(100, "leader", 0, 0, null, 101, -1); - assertEquals(expected, raftBehavior); + follower = createBehavior(context); - // Also expect an AppendEntriesReply to be sent where success is false - final Boolean out = new ExpectMsg(duration("1 seconds"), - "AppendEntriesReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof AppendEntriesReply) { - AppendEntriesReply reply = (AppendEntriesReply) in; - return reply.isSuccess(); - } else { - throw noMatch(); - } - } - }.get(); + RaftActorBehavior newBehavior = follower.handleMessage(leaderActor, appendEntries); - assertEquals(false, out); + Assert.assertSame(follower, newBehavior); + AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(leaderActor, + AppendEntriesReply.class); - }}; + assertEquals("isSuccess", false, reply.isSuccess()); } - - /** * This test verifies that when a new AppendEntries message is received with * new entries and the logs of the sender and receiver match that the new @@ -260,278 +190,201 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { * @throws Exception */ @Test - public void testHandleAppendEntriesAddNewEntries() throws Exception { - new JavaTestKit(getSystem()) {{ - - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); - - // First set the receivers term to lower number - context.getTermInformation().update(1, "test"); - - // Prepare the receivers log - MockRaftActorContext.SimpleReplicatedLog log = - new MockRaftActorContext.SimpleReplicatedLog(); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero"))); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 1, new MockRaftActorContext.MockPayload("one"))); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 2, new MockRaftActorContext.MockPayload("two"))); - - context.setReplicatedLog(log); - - // Prepare the entries to be sent with AppendEntries - List entries = new ArrayList<>(); - entries.add( - new MockRaftActorContext.MockReplicatedLogEntry(1, 3, new MockRaftActorContext.MockPayload("three"))); - entries.add( - new MockRaftActorContext.MockReplicatedLogEntry(1, 4, new MockRaftActorContext.MockPayload("four"))); - - // Send appendEntries with the same term as was set on the receiver - // before the new behavior was created (1 in this case) - // This will not work for a Candidate because as soon as a Candidate - // is created it increments the term - AppendEntries appendEntries = - new AppendEntries(1, "leader-1", 2, 1, entries, 4); - - RaftActorBehavior behavior = createBehavior(context); - - // Send an unknown message so that the state of the RaftActor remains unchanged - RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); - - RaftActorBehavior raftBehavior = - behavior.handleMessage(getRef(), appendEntries); - - assertEquals(expected, raftBehavior); - assertEquals(5, log.last().getIndex() + 1); - assertNotNull(log.get(3)); - assertNotNull(log.get(4)); - - // Also expect an AppendEntriesReply to be sent where success is false - final Boolean out = new ExpectMsg(duration("1 seconds"), - "AppendEntriesReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof AppendEntriesReply) { - AppendEntriesReply reply = (AppendEntriesReply) in; - return reply.isSuccess(); - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(true, out); - - - }}; - } + public void testHandleAppendEntriesAddNewEntries() { + logStart("testHandleAppendEntriesAddNewEntries"); + + MockRaftActorContext context = createActorContext(); + + // First set the receivers term to lower number + context.getTermInformation().update(1, "test"); + // Prepare the receivers log + MockRaftActorContext.SimpleReplicatedLog log = new MockRaftActorContext.SimpleReplicatedLog(); + log.append(newReplicatedLogEntry(1, 0, "zero")); + log.append(newReplicatedLogEntry(1, 1, "one")); + log.append(newReplicatedLogEntry(1, 2, "two")); + context.setReplicatedLog(log); + + // Prepare the entries to be sent with AppendEntries + List entries = new ArrayList<>(); + entries.add(newReplicatedLogEntry(1, 3, "three")); + entries.add(newReplicatedLogEntry(1, 4, "four")); + + // Send appendEntries with the same term as was set on the receiver + // before the new behavior was created (1 in this case) + // This will not work for a Candidate because as soon as a Candidate + // is created it increments the term + AppendEntries appendEntries = new AppendEntries(1, "leader-1", 2, 1, entries, 4, -1); + + follower = createBehavior(context); + + RaftActorBehavior newBehavior = follower.handleMessage(leaderActor, appendEntries); + + Assert.assertSame(follower, newBehavior); + + assertEquals("Next index", 5, log.last().getIndex() + 1); + assertEquals("Entry 3", entries.get(0), log.get(3)); + assertEquals("Entry 4", entries.get(1), log.get(4)); + + expectAndVerifyAppendEntriesReply(1, true, context.getId(), 1, 4); + } /** * This test verifies that when a new AppendEntries message is received with * new entries and the logs of the sender and receiver are out-of-sync that * the log is first corrected by removing the out of sync entries from the * log and then adding in the new entries sent with the AppendEntries message - * - * @throws Exception */ @Test - public void testHandleAppendEntriesCorrectReceiverLogEntries() - throws Exception { - new JavaTestKit(getSystem()) {{ - - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); - - // First set the receivers term to lower number - context.getTermInformation().update(2, "test"); - - // Prepare the receivers log - MockRaftActorContext.SimpleReplicatedLog log = - new MockRaftActorContext.SimpleReplicatedLog(); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero"))); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 1, new MockRaftActorContext.MockPayload("one"))); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 2, new MockRaftActorContext.MockPayload("two"))); - - context.setReplicatedLog(log); - - // Prepare the entries to be sent with AppendEntries - List entries = new ArrayList<>(); - entries.add( - new MockRaftActorContext.MockReplicatedLogEntry(2, 2, new MockRaftActorContext.MockPayload("two-1"))); - entries.add( - new MockRaftActorContext.MockReplicatedLogEntry(2, 3, new MockRaftActorContext.MockPayload("three"))); - - // Send appendEntries with the same term as was set on the receiver - // before the new behavior was created (1 in this case) - // This will not work for a Candidate because as soon as a Candidate - // is created it increments the term - AppendEntries appendEntries = - new AppendEntries(2, "leader-1", 1, 1, entries, 3); - - RaftActorBehavior behavior = createBehavior(context); - - // Send an unknown message so that the state of the RaftActor remains unchanged - RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); - - RaftActorBehavior raftBehavior = - behavior.handleMessage(getRef(), appendEntries); - - assertEquals(expected, raftBehavior); - - // The entry at index 2 will be found out-of-sync with the leader - // and will be removed - // Then the two new entries will be added to the log - // Thus making the log to have 4 entries - assertEquals(4, log.last().getIndex() + 1); - assertNotNull(log.get(2)); - - assertEquals("one", log.get(1).getData().toString()); - - // Check that the entry at index 2 has the new data - assertEquals("two-1", log.get(2).getData().toString()); - - assertEquals("three", log.get(3).getData().toString()); - - assertNotNull(log.get(3)); - - // Also expect an AppendEntriesReply to be sent where success is false - final Boolean out = new ExpectMsg(duration("1 seconds"), - "AppendEntriesReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof AppendEntriesReply) { - AppendEntriesReply reply = (AppendEntriesReply) in; - return reply.isSuccess(); - } else { - throw noMatch(); - } - } - }.get(); - - assertEquals(true, out); - - - }}; + public void testHandleAppendEntriesCorrectReceiverLogEntries() { + logStart("testHandleAppendEntriesCorrectReceiverLogEntries"); + + MockRaftActorContext context = createActorContext(); + + // First set the receivers term to lower number + context.getTermInformation().update(1, "test"); + + // Prepare the receivers log + MockRaftActorContext.SimpleReplicatedLog log = new MockRaftActorContext.SimpleReplicatedLog(); + log.append(newReplicatedLogEntry(1, 0, "zero")); + log.append(newReplicatedLogEntry(1, 1, "one")); + log.append(newReplicatedLogEntry(1, 2, "two")); + + context.setReplicatedLog(log); + + // Prepare the entries to be sent with AppendEntries + List entries = new ArrayList<>(); + entries.add(newReplicatedLogEntry(2, 2, "two-1")); + entries.add(newReplicatedLogEntry(2, 3, "three")); + + // Send appendEntries with the same term as was set on the receiver + // before the new behavior was created (1 in this case) + // This will not work for a Candidate because as soon as a Candidate + // is created it increments the term + AppendEntries appendEntries = new AppendEntries(2, "leader", 1, 1, entries, 3, -1); + + follower = createBehavior(context); + + RaftActorBehavior newBehavior = follower.handleMessage(leaderActor, appendEntries); + + Assert.assertSame(follower, newBehavior); + + // The entry at index 2 will be found out-of-sync with the leader + // and will be removed + // Then the two new entries will be added to the log + // Thus making the log to have 4 entries + assertEquals("Next index", 4, log.last().getIndex() + 1); + //assertEquals("Entry 2", entries.get(0), log.get(2)); + + assertEquals("Entry 1 data", "one", log.get(1).getData().toString()); + + // Check that the entry at index 2 has the new data + assertEquals("Entry 2", entries.get(0), log.get(2)); + + assertEquals("Entry 3", entries.get(1), log.get(3)); + + expectAndVerifyAppendEntriesReply(2, true, context.getId(), 2, 3); } @Test public void testHandleAppendEntriesPreviousLogEntryMissing(){ - new JavaTestKit(getSystem()) {{ + logStart("testHandleAppendEntriesPreviousLogEntryMissing"); - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); + MockRaftActorContext context = createActorContext(); - // Prepare the receivers log - MockRaftActorContext.SimpleReplicatedLog log = - new MockRaftActorContext.SimpleReplicatedLog(); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero"))); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 1, new MockRaftActorContext.MockPayload("one"))); - log.append( - new MockRaftActorContext.MockReplicatedLogEntry(1, 2, new MockRaftActorContext.MockPayload("two"))); + // Prepare the receivers log + MockRaftActorContext.SimpleReplicatedLog log = new MockRaftActorContext.SimpleReplicatedLog(); + log.append(newReplicatedLogEntry(1, 0, "zero")); + log.append(newReplicatedLogEntry(1, 1, "one")); + log.append(newReplicatedLogEntry(1, 2, "two")); - context.setReplicatedLog(log); + context.setReplicatedLog(log); - // Prepare the entries to be sent with AppendEntries - List entries = new ArrayList<>(); - entries.add( - new MockRaftActorContext.MockReplicatedLogEntry(1, 4, new MockRaftActorContext.MockPayload("two-1"))); + // Prepare the entries to be sent with AppendEntries + List entries = new ArrayList<>(); + entries.add(newReplicatedLogEntry(1, 4, "four")); - AppendEntries appendEntries = - new AppendEntries(1, "leader-1", 3, 1, entries, 4); + AppendEntries appendEntries = new AppendEntries(1, "leader", 3, 1, entries, 4, -1); - RaftActorBehavior behavior = createBehavior(context); + follower = createBehavior(context); - // Send an unknown message so that the state of the RaftActor remains unchanged - RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); + RaftActorBehavior newBehavior = follower.handleMessage(leaderActor, appendEntries); - RaftActorBehavior raftBehavior = - behavior.handleMessage(getRef(), appendEntries); + Assert.assertSame(follower, newBehavior); - assertEquals(expected, raftBehavior); + expectAndVerifyAppendEntriesReply(1, false, context.getId(), 1, 2); + } - // Also expect an AppendEntriesReply to be sent where success is false - final Boolean out = new ExpectMsg(duration("1 seconds"), - "AppendEntriesReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof AppendEntriesReply) { - AppendEntriesReply reply = (AppendEntriesReply) in; - return reply.isSuccess(); - } else { - throw noMatch(); - } - } - }.get(); + @Test + public void testHandleAppendEntriesWithExistingLogEntry() { + logStart("testHandleAppendEntriesWithExistingLogEntry"); - assertEquals(false, out); + MockRaftActorContext context = createActorContext(); - }}; + context.getTermInformation().update(1, "test"); - } + // Prepare the receivers log + MockRaftActorContext.SimpleReplicatedLog log = new MockRaftActorContext.SimpleReplicatedLog(); + log.append(newReplicatedLogEntry(1, 0, "zero")); + log.append(newReplicatedLogEntry(1, 1, "one")); - @Test - public void testHandleAppendAfterInstallingSnapshot(){ - new JavaTestKit(getSystem()) {{ + context.setReplicatedLog(log); - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); + // Send the last entry again. + List entries = Arrays.asList(newReplicatedLogEntry(1, 1, "one")); + follower = createBehavior(context); - // Prepare the receivers log - MockRaftActorContext.SimpleReplicatedLog log = - new MockRaftActorContext.SimpleReplicatedLog(); + follower.handleMessage(leaderActor, new AppendEntries(1, "leader", 0, 1, entries, 1, -1)); - // Set up a log as if it has been snapshotted - log.setSnapshotIndex(3); - log.setSnapshotTerm(1); + assertEquals("Next index", 2, log.last().getIndex() + 1); + assertEquals("Entry 1", entries.get(0), log.get(1)); - context.setReplicatedLog(log); + expectAndVerifyAppendEntriesReply(1, true, context.getId(), 1, 1); - // Prepare the entries to be sent with AppendEntries - List entries = new ArrayList<>(); - entries.add( - new MockRaftActorContext.MockReplicatedLogEntry(1, 4, new MockRaftActorContext.MockPayload("two-1"))); + // Send the last entry again and also a new one. - AppendEntries appendEntries = - new AppendEntries(1, "leader-1", 3, 1, entries, 4); + entries = Arrays.asList(newReplicatedLogEntry(1, 1, "one"), newReplicatedLogEntry(1, 2, "two")); - RaftActorBehavior behavior = createBehavior(context); + leaderActor.underlyingActor().clear(); + follower.handleMessage(leaderActor, new AppendEntries(1, "leader", 0, 1, entries, 2, -1)); - // Send an unknown message so that the state of the RaftActor remains unchanged - RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); + assertEquals("Next index", 3, log.last().getIndex() + 1); + assertEquals("Entry 1", entries.get(0), log.get(1)); + assertEquals("Entry 2", entries.get(1), log.get(2)); - RaftActorBehavior raftBehavior = - behavior.handleMessage(getRef(), appendEntries); + expectAndVerifyAppendEntriesReply(1, true, context.getId(), 1, 2); + } - assertEquals(expected, raftBehavior); + @Test + public void testHandleAppendAfterInstallingSnapshot(){ + logStart("testHandleAppendAfterInstallingSnapshot"); - // Also expect an AppendEntriesReply to be sent where success is false - final Boolean out = new ExpectMsg(duration("1 seconds"), - "AppendEntriesReply") { - // do not put code outside this method, will run afterwards - protected Boolean match(Object in) { - if (in instanceof AppendEntriesReply) { - AppendEntriesReply reply = (AppendEntriesReply) in; - return reply.isSuccess(); - } else { - throw noMatch(); - } - } - }.get(); + MockRaftActorContext context = createActorContext(); - assertEquals(true, out); + // Prepare the receivers log + MockRaftActorContext.SimpleReplicatedLog log = new MockRaftActorContext.SimpleReplicatedLog(); - }}; + // Set up a log as if it has been snapshotted + log.setSnapshotIndex(3); + log.setSnapshotTerm(1); + context.setReplicatedLog(log); + + // Prepare the entries to be sent with AppendEntries + List entries = new ArrayList<>(); + entries.add(newReplicatedLogEntry(1, 4, "four")); + + AppendEntries appendEntries = new AppendEntries(1, "leader", 3, 1, entries, 4, 3); + + follower = createBehavior(context); + + RaftActorBehavior newBehavior = follower.handleMessage(leaderActor, appendEntries); + + Assert.assertSame(follower, newBehavior); + + expectAndVerifyAppendEntriesReply(1, true, context.getId(), 1, 4); } @@ -543,182 +396,137 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { */ @Test public void testHandleInstallSnapshot() throws Exception { - JavaTestKit javaTestKit = new JavaTestKit(getSystem()) {{ - - ActorRef leaderActor = getSystem().actorOf(Props.create( - MessageCollectorActor.class)); - - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(getRef()); - - Follower follower = (Follower)createBehavior(context); - - HashMap followerSnapshot = new HashMap<>(); - followerSnapshot.put("1", "A"); - followerSnapshot.put("2", "B"); - followerSnapshot.put("3", "C"); - - ByteString bsSnapshot = toByteString(followerSnapshot); - ByteString chunkData = ByteString.EMPTY; - int offset = 0; - int snapshotLength = bsSnapshot.size(); - int i = 1; - int chunkIndex = 1; - - do { - chunkData = getNextChunk(bsSnapshot, offset); - final InstallSnapshot installSnapshot = - new InstallSnapshot(1, "leader-1", i, 1, - chunkData, chunkIndex, 3); - follower.handleMessage(leaderActor, installSnapshot); - offset = offset + 50; - i++; - chunkIndex++; - } while ((offset+50) < snapshotLength); - - final InstallSnapshot installSnapshot3 = new InstallSnapshot(1, "leader-1", 3, 1, chunkData, chunkIndex, 3); - follower.handleMessage(leaderActor, installSnapshot3); - - String[] matches = new ReceiveWhile(String.class, duration("2 seconds")) { - @Override - protected String match(Object o) throws Exception { - if (o instanceof ApplySnapshot) { - ApplySnapshot as = (ApplySnapshot)o; - if (as.getSnapshot().getLastIndex() != installSnapshot3.getLastIncludedIndex()) { - return "applySnapshot-lastIndex-mismatch"; - } - if (as.getSnapshot().getLastAppliedTerm() != installSnapshot3.getLastIncludedTerm()) { - return "applySnapshot-lastAppliedTerm-mismatch"; - } - if (as.getSnapshot().getLastAppliedIndex() != installSnapshot3.getLastIncludedIndex()) { - return "applySnapshot-lastAppliedIndex-mismatch"; - } - if (as.getSnapshot().getLastTerm() != installSnapshot3.getLastIncludedTerm()) { - return "applySnapshot-lastTerm-mismatch"; - } - return "applySnapshot"; - } - - return "ignoreCase"; - } - }.get(); - - // Verify that after a snapshot is successfully applied the collected snapshot chunks is reset to empty - assertEquals(ByteString.EMPTY, follower.getSnapshotChunksCollected()); - - String applySnapshotMatch = ""; - for (String reply: matches) { - if (reply.startsWith("applySnapshot")) { - applySnapshotMatch = reply; - } - } - - assertEquals("applySnapshot", applySnapshotMatch); - - Object messages = executeLocalOperation(leaderActor, "get-all-messages"); - - assertNotNull(messages); - assertTrue(messages instanceof List); - List listMessages = (List) messages; - - int installSnapshotReplyReceivedCount = 0; - for (Object message: listMessages) { - if (message instanceof InstallSnapshotReply) { - ++installSnapshotReplyReceivedCount; - } - } + logStart("testHandleInstallSnapshot"); + + MockRaftActorContext context = createActorContext(); + + follower = createBehavior(context); + + HashMap followerSnapshot = new HashMap<>(); + followerSnapshot.put("1", "A"); + followerSnapshot.put("2", "B"); + followerSnapshot.put("3", "C"); + + ByteString bsSnapshot = toByteString(followerSnapshot); + int offset = 0; + int snapshotLength = bsSnapshot.size(); + int chunkSize = 50; + int totalChunks = (snapshotLength / chunkSize) + ((snapshotLength % chunkSize) > 0 ? 1 : 0); + int lastIncludedIndex = 1; + int chunkIndex = 1; + InstallSnapshot lastInstallSnapshot = null; + + for(int i = 0; i < totalChunks; i++) { + ByteString chunkData = getNextChunk(bsSnapshot, offset, chunkSize); + lastInstallSnapshot = new InstallSnapshot(1, "leader", lastIncludedIndex, 1, + chunkData, chunkIndex, totalChunks); + follower.handleMessage(leaderActor, lastInstallSnapshot); + offset = offset + 50; + lastIncludedIndex++; + chunkIndex++; + } - assertEquals(3, installSnapshotReplyReceivedCount); + ApplySnapshot applySnapshot = MessageCollectorActor.expectFirstMatching(followerActor, + ApplySnapshot.class); + Snapshot snapshot = applySnapshot.getSnapshot(); + assertEquals("getLastIndex", lastInstallSnapshot.getLastIncludedIndex(), snapshot.getLastIndex()); + assertEquals("getLastIncludedTerm", lastInstallSnapshot.getLastIncludedTerm(), + snapshot.getLastAppliedTerm()); + assertEquals("getLastAppliedIndex", lastInstallSnapshot.getLastIncludedIndex(), + snapshot.getLastAppliedIndex()); + assertEquals("getLastTerm", lastInstallSnapshot.getLastIncludedTerm(), snapshot.getLastTerm()); + Assert.assertArrayEquals("getState", bsSnapshot.toByteArray(), snapshot.getState()); + + List replies = MessageCollectorActor.getAllMatching( + leaderActor, InstallSnapshotReply.class); + assertEquals("InstallSnapshotReply count", totalChunks, replies.size()); + + chunkIndex = 1; + for(InstallSnapshotReply reply: replies) { + assertEquals("getChunkIndex", chunkIndex++, reply.getChunkIndex()); + assertEquals("getTerm", 1, reply.getTerm()); + assertEquals("isSuccess", true, reply.isSuccess()); + assertEquals("getFollowerId", context.getId(), reply.getFollowerId()); + } - }}; + Assert.assertNull("Expected null SnapshotTracker", ((Follower)follower).getSnapshotTracker()); } @Test public void testHandleOutOfSequenceInstallSnapshot() throws Exception { - JavaTestKit javaTestKit = new JavaTestKit(getSystem()) { - { - - ActorRef leaderActor = getSystem().actorOf(Props.create( - MessageCollectorActor.class)); - - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(getRef()); + logStart("testHandleOutOfSequenceInstallSnapshot"); - Follower follower = (Follower) createBehavior(context); + MockRaftActorContext context = createActorContext(); - HashMap followerSnapshot = new HashMap<>(); - followerSnapshot.put("1", "A"); - followerSnapshot.put("2", "B"); - followerSnapshot.put("3", "C"); + follower = createBehavior(context); - ByteString bsSnapshot = toByteString(followerSnapshot); + HashMap followerSnapshot = new HashMap<>(); + followerSnapshot.put("1", "A"); + followerSnapshot.put("2", "B"); + followerSnapshot.put("3", "C"); - final InstallSnapshot installSnapshot = new InstallSnapshot(1, "leader-1", 3, 1, getNextChunk(bsSnapshot, 10), 3, 3); - follower.handleMessage(leaderActor, installSnapshot); + ByteString bsSnapshot = toByteString(followerSnapshot); - Object messages = executeLocalOperation(leaderActor, "get-all-messages"); + InstallSnapshot installSnapshot = new InstallSnapshot(1, "leader", 3, 1, + getNextChunk(bsSnapshot, 10, 50), 3, 3); + follower.handleMessage(leaderActor, installSnapshot); - assertNotNull(messages); - assertTrue(messages instanceof List); - List listMessages = (List) messages; + InstallSnapshotReply reply = MessageCollectorActor.expectFirstMatching(leaderActor, + InstallSnapshotReply.class); - int installSnapshotReplyReceivedCount = 0; - for (Object message: listMessages) { - if (message instanceof InstallSnapshotReply) { - ++installSnapshotReplyReceivedCount; - } - } + assertEquals("isSuccess", false, reply.isSuccess()); + assertEquals("getChunkIndex", -1, reply.getChunkIndex()); + assertEquals("getTerm", 1, reply.getTerm()); + assertEquals("getFollowerId", context.getId(), reply.getFollowerId()); - assertEquals(1, installSnapshotReplyReceivedCount); - InstallSnapshotReply reply = (InstallSnapshotReply) listMessages.get(0); - assertEquals(false, reply.isSuccess()); - assertEquals(-1, reply.getChunkIndex()); - assertEquals(ByteString.EMPTY, follower.getSnapshotChunksCollected()); - - - }}; + Assert.assertNull("Expected null SnapshotTracker", ((Follower)follower).getSnapshotTracker()); } - public Object executeLocalOperation(ActorRef actor, Object message) throws Exception { - return MessageCollectorActor.getAllMessages(actor); - } - - public ByteString getNextChunk (ByteString bs, int offset){ + public ByteString getNextChunk (ByteString bs, int offset, int chunkSize){ int snapshotLength = bs.size(); int start = offset; - int size = 50; - if (50 > snapshotLength) { + int size = chunkSize; + if (chunkSize > snapshotLength) { size = snapshotLength; } else { - if ((start + 50) > snapshotLength) { + if ((start + chunkSize) > snapshotLength) { size = snapshotLength - start; } } return bs.substring(start, start + size); } - private ByteString toByteString(Map state) { - ByteArrayOutputStream b = null; - ObjectOutputStream o = null; - try { - try { - b = new ByteArrayOutputStream(); - o = new ObjectOutputStream(b); - o.writeObject(state); - byte[] snapshotBytes = b.toByteArray(); - return ByteString.copyFrom(snapshotBytes); - } finally { - if (o != null) { - o.flush(); - o.close(); - } - if (b != null) { - b.close(); - } - } - } catch (IOException e) { - org.junit.Assert.fail("IOException in converting Hashmap to Bytestring:" + e); - } - return null; + private void expectAndVerifyAppendEntriesReply(int expTerm, boolean expSuccess, + String expFollowerId, long expLogLastTerm, long expLogLastIndex) { + + AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(leaderActor, + AppendEntriesReply.class); + + assertEquals("isSuccess", expSuccess, reply.isSuccess()); + assertEquals("getTerm", expTerm, reply.getTerm()); + assertEquals("getFollowerId", expFollowerId, reply.getFollowerId()); + assertEquals("getLogLastTerm", expLogLastTerm, reply.getLogLastTerm()); + assertEquals("getLogLastIndex", expLogLastIndex, reply.getLogLastIndex()); + } + + private ReplicatedLogEntry newReplicatedLogEntry(long term, long index, String data) { + return new MockRaftActorContext.MockReplicatedLogEntry(term, index, + new MockRaftActorContext.MockPayload(data)); + } + + @Override + protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(RaftActorContext actorContext, + ActorRef actorRef, RaftRPC rpc) throws Exception { + super.assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, actorRef, rpc); + + String expVotedFor = RequestVote.class.isInstance(rpc) ? ((RequestVote)rpc).getCandidateId() : null; + assertEquals("New votedFor", expVotedFor, actorContext.getTermInformation().getVotedFor()); + } + + @Override + protected void handleAppendEntriesAddSameEntryToLogReply(TestActorRef replyActor) + throws Exception { + AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(replyActor, AppendEntriesReply.class); + assertEquals("isSuccess", true, reply.isSuccess()); } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/IsolatedLeaderTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/IsolatedLeaderTest.java index 708068a789..e16d765cde 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/IsolatedLeaderTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/IsolatedLeaderTest.java @@ -7,135 +7,155 @@ */ package org.opendaylight.controller.cluster.raft.behaviors; +import static org.junit.Assert.assertEquals; import akka.actor.ActorRef; import akka.actor.Props; -import akka.testkit.JavaTestKit; +import akka.testkit.TestActorRef; import java.util.HashMap; import java.util.Map; +import org.junit.After; import org.junit.Test; +import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; import org.opendaylight.controller.cluster.raft.RaftActorContext; import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; -import org.opendaylight.controller.cluster.raft.utils.DoNothingActor; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; + +public class IsolatedLeaderTest extends AbstractLeaderTest { + + private final TestActorRef leaderActor = actorFactory.createTestActor( + Props.create(MessageCollectorActor.class), actorFactory.generateActorId("leader")); -public class IsolatedLeaderTest extends AbstractRaftActorBehaviorTest { + private final TestActorRef senderActor = actorFactory.createTestActor( + Props.create(MessageCollectorActor.class), actorFactory.generateActorId("sender")); - private ActorRef leaderActor = - getSystem().actorOf(Props.create(DoNothingActor.class)); + private AbstractLeader isolatedLeader; - private ActorRef senderActor = - getSystem().actorOf(Props.create(DoNothingActor.class)); + @Override + @After + public void tearDown() throws Exception { + if(isolatedLeader != null) { + isolatedLeader.close(); + } + + super.tearDown(); + } @Override - protected RaftActorBehavior createBehavior( - RaftActorContext actorContext) { - return new Leader(actorContext); + protected RaftActorBehavior createBehavior(RaftActorContext actorContext) { + return new IsolatedLeader(actorContext); } @Override - protected RaftActorContext createActorContext() { + protected MockRaftActorContext createActorContext() { return createActorContext(leaderActor); } + @Override + protected MockRaftActorContext createActorContext(ActorRef actor) { + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + configParams.setElectionTimeoutFactor(100000); + MockRaftActorContext context = new MockRaftActorContext("isolated-leader", getSystem(), actor); + context.setConfigParams(configParams); + return context; + } @Test - public void testHandleMessageWithThreeMembers() { - new JavaTestKit(getSystem()) {{ - String followerAddress1 = "akka://test/user/$a"; - String followerAddress2 = "akka://test/user/$b"; - - MockRaftActorContext leaderActorContext = (MockRaftActorContext) createActorContext(); - Map peerAddresses = new HashMap<>(); - peerAddresses.put("follower-1", followerAddress1); - peerAddresses.put("follower-2", followerAddress2); - leaderActorContext.setPeerAddresses(peerAddresses); - - IsolatedLeader isolatedLeader = new IsolatedLeader(leaderActorContext); - assertTrue(isolatedLeader.state() == RaftState.IsolatedLeader); - - // in a 3 node cluster, even if 1 follower is returns a reply, the isolatedLeader is not isolated - RaftActorBehavior behavior = isolatedLeader.handleMessage(senderActor, + public void testHandleMessageWithThreeMembers() throws Exception { + String followerAddress1 = "akka://test/user/$a"; + String followerAddress2 = "akka://test/user/$b"; + + MockRaftActorContext leaderActorContext = createActorContext(); + Map peerAddresses = new HashMap<>(); + peerAddresses.put("follower-1", followerAddress1); + peerAddresses.put("follower-2", followerAddress2); + leaderActorContext.setPeerAddresses(peerAddresses); + + isolatedLeader = new IsolatedLeader(leaderActorContext); + assertEquals("Raft state", RaftState.IsolatedLeader, isolatedLeader.state()); + + // in a 3 node cluster, even if 1 follower is returns a reply, the isolatedLeader is not isolated + RaftActorBehavior behavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-1", isolatedLeader.lastTerm() - 1, true, - isolatedLeader.lastIndex() - 1, isolatedLeader.lastTerm() - 1)); + isolatedLeader.lastIndex() - 1, isolatedLeader.lastTerm() - 1)); + + assertEquals("Raft state", RaftState.Leader, behavior.state()); - assertEquals(RaftState.Leader, behavior.state()); + isolatedLeader.close(); + isolatedLeader = (AbstractLeader) behavior; - behavior = isolatedLeader.handleMessage(senderActor, + behavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-2", isolatedLeader.lastTerm() - 1, true, - isolatedLeader.lastIndex() -1, isolatedLeader.lastTerm() -1 )); + isolatedLeader.lastIndex() -1, isolatedLeader.lastTerm() -1 )); - assertEquals(RaftState.Leader, behavior.state()); - }}; + assertEquals("Raft state", RaftState.Leader, behavior.state()); } @Test - public void testHandleMessageWithFiveMembers() { - new JavaTestKit(getSystem()) {{ - - String followerAddress1 = "akka://test/user/$a"; - String followerAddress2 = "akka://test/user/$b"; - String followerAddress3 = "akka://test/user/$c"; - String followerAddress4 = "akka://test/user/$d"; - - MockRaftActorContext leaderActorContext = (MockRaftActorContext) createActorContext(); - Map peerAddresses = new HashMap<>(); - peerAddresses.put("follower-1", followerAddress1); - peerAddresses.put("follower-2", followerAddress2); - peerAddresses.put("follower-3", followerAddress3); - peerAddresses.put("follower-4", followerAddress4); - leaderActorContext.setPeerAddresses(peerAddresses); - - IsolatedLeader isolatedLeader = new IsolatedLeader(leaderActorContext); - assertEquals(RaftState.IsolatedLeader, isolatedLeader.state()); - - // in a 5 member cluster, atleast 2 followers need to be active and return a reply - RaftActorBehavior behavior = isolatedLeader.handleMessage(senderActor, + public void testHandleMessageWithFiveMembers() throws Exception { + String followerAddress1 = "akka://test/user/$a"; + String followerAddress2 = "akka://test/user/$b"; + String followerAddress3 = "akka://test/user/$c"; + String followerAddress4 = "akka://test/user/$d"; + + MockRaftActorContext leaderActorContext = createActorContext(); + Map peerAddresses = new HashMap<>(); + peerAddresses.put("follower-1", followerAddress1); + peerAddresses.put("follower-2", followerAddress2); + peerAddresses.put("follower-3", followerAddress3); + peerAddresses.put("follower-4", followerAddress4); + leaderActorContext.setPeerAddresses(peerAddresses); + + isolatedLeader = new IsolatedLeader(leaderActorContext); + assertEquals("Raft state", RaftState.IsolatedLeader, isolatedLeader.state()); + + // in a 5 member cluster, atleast 2 followers need to be active and return a reply + RaftActorBehavior behavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-1", isolatedLeader.lastTerm() - 1, true, - isolatedLeader.lastIndex() -1, isolatedLeader.lastTerm() -1 )); + isolatedLeader.lastIndex() -1, isolatedLeader.lastTerm() -1 )); - assertEquals(RaftState.IsolatedLeader, behavior.state()); + assertEquals("Raft state", RaftState.IsolatedLeader, behavior.state()); - behavior = isolatedLeader.handleMessage(senderActor, + behavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-2", isolatedLeader.lastTerm() - 1, true, - isolatedLeader.lastIndex() -1, isolatedLeader.lastTerm() -1 )); + isolatedLeader.lastIndex() -1, isolatedLeader.lastTerm() -1 )); + + assertEquals("Raft state", RaftState.Leader, behavior.state()); - assertEquals(RaftState.Leader, behavior.state()); + isolatedLeader.close(); + isolatedLeader = (AbstractLeader) behavior; - behavior = isolatedLeader.handleMessage(senderActor, + behavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-3", isolatedLeader.lastTerm() - 1, true, - isolatedLeader.lastIndex() -1, isolatedLeader.lastTerm() -1 )); + isolatedLeader.lastIndex() -1, isolatedLeader.lastTerm() -1 )); - assertEquals(RaftState.Leader, behavior.state()); - }}; + assertEquals("Raft state", RaftState.Leader, behavior.state()); } @Test - public void testHandleMessageFromAnotherLeader() { - new JavaTestKit(getSystem()) {{ - String followerAddress1 = "akka://test/user/$a"; - String followerAddress2 = "akka://test/user/$b"; - - MockRaftActorContext leaderActorContext = (MockRaftActorContext) createActorContext(); - Map peerAddresses = new HashMap<>(); - peerAddresses.put("follower-1", followerAddress1); - peerAddresses.put("follower-2", followerAddress2); - leaderActorContext.setPeerAddresses(peerAddresses); - - IsolatedLeader isolatedLeader = new IsolatedLeader(leaderActorContext); - assertTrue(isolatedLeader.state() == RaftState.IsolatedLeader); - - // if an append-entries reply is received by the isolated-leader, and that reply - // has a term > than its own term, then IsolatedLeader switches to Follower - // bowing itself to another leader in the cluster - RaftActorBehavior behavior = isolatedLeader.handleMessage(senderActor, + public void testHandleMessageFromAnotherLeader() throws Exception { + String followerAddress1 = "akka://test/user/$a"; + String followerAddress2 = "akka://test/user/$b"; + + MockRaftActorContext leaderActorContext = createActorContext(); + Map peerAddresses = new HashMap<>(); + peerAddresses.put("follower-1", followerAddress1); + peerAddresses.put("follower-2", followerAddress2); + leaderActorContext.setPeerAddresses(peerAddresses); + + isolatedLeader = new IsolatedLeader(leaderActorContext); + assertEquals("Raft state", RaftState.IsolatedLeader, isolatedLeader.state()); + + // if an append-entries reply is received by the isolated-leader, and that reply + // has a term > than its own term, then IsolatedLeader switches to Follower + // bowing itself to another leader in the cluster + RaftActorBehavior behavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-1", isolatedLeader.lastTerm() + 1, true, - isolatedLeader.lastIndex() + 1, isolatedLeader.lastTerm() + 1)); + isolatedLeader.lastIndex() + 1, isolatedLeader.lastTerm() + 1)); - assertEquals(RaftState.Follower, behavior.state()); - }}; + assertEquals("Raft state", RaftState.Follower, behavior.state()); + behavior.close(); } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderElectionScenariosTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderElectionScenariosTest.java new file mode 100644 index 0000000000..3aac005179 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderElectionScenariosTest.java @@ -0,0 +1,795 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.raft.behaviors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.Props; +import akka.dispatch.Dispatchers; +import akka.testkit.JavaTestKit; +import akka.testkit.TestActorRef; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Uninterruptibles; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Test; +import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockReplicatedLogEntry; +import org.opendaylight.controller.cluster.raft.MockRaftActorContext.SimpleReplicatedLog; +import org.opendaylight.controller.cluster.raft.RaftActorContext; +import org.opendaylight.controller.cluster.raft.RaftState; +import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout; +import org.opendaylight.controller.cluster.raft.base.messages.SendHeartBeat; +import org.opendaylight.controller.cluster.raft.messages.AppendEntries; +import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; +import org.opendaylight.controller.cluster.raft.messages.RequestVote; +import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; +import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.impl.SimpleLogger; +import scala.concurrent.duration.FiniteDuration; + +/** + * Tests various leader election scenarios. + * + * @author Thomas Pantelis + */ +public class LeaderElectionScenariosTest { + + private static final int HEARTBEAT_INTERVAL = 50; + + public static class MemberActor extends MessageCollectorActor { + + volatile RaftActorBehavior behavior; + Map, CountDownLatch> messagesReceivedLatches = new ConcurrentHashMap<>(); + Map, Boolean> dropMessagesToBehavior = new ConcurrentHashMap<>(); + CountDownLatch behaviorStateChangeLatch; + + public static Props props() { + return Props.create(MemberActor.class).withDispatcher(Dispatchers.DefaultDispatcherId()); + } + + @Override + public void onReceive(Object message) throws Exception { + // Ignore scheduled SendHeartBeat messages. + if(message instanceof SendHeartBeat) { + return; + } + + try { + if(behavior != null && !dropMessagesToBehavior.containsKey(message.getClass())) { + RaftActorBehavior oldBehavior = behavior; + behavior = behavior.handleMessage(getSender(), message); + if(behavior != oldBehavior && behaviorStateChangeLatch != null) { + behaviorStateChangeLatch.countDown(); + } + } + } finally { + super.onReceive(message); + + CountDownLatch latch = messagesReceivedLatches.get(message.getClass()); + if(latch != null) { + latch.countDown(); + } + } + } + + void expectBehaviorStateChange() { + behaviorStateChangeLatch = new CountDownLatch(1); + } + + void waitForBehaviorStateChange() { + assertTrue("Expected behavior state change", + Uninterruptibles.awaitUninterruptibly(behaviorStateChangeLatch, 5, TimeUnit.SECONDS)); + } + + void expectMessageClass(Class expClass, int expCount) { + messagesReceivedLatches.put(expClass, new CountDownLatch(expCount)); + } + + void waitForExpectedMessages(Class expClass) { + CountDownLatch latch = messagesReceivedLatches.get(expClass); + assertNotNull("No messages received for " + expClass, latch); + assertTrue("Missing messages of type " + expClass, + Uninterruptibles.awaitUninterruptibly(latch, 5, TimeUnit.SECONDS)); + } + + void dropMessagesToBehavior(Class msgClass) { + dropMessagesToBehavior(msgClass, 1); + } + + void dropMessagesToBehavior(Class msgClass, int expCount) { + expectMessageClass(msgClass, expCount); + dropMessagesToBehavior.put(msgClass, Boolean.TRUE); + } + + void clearDropMessagesToBehavior() { + dropMessagesToBehavior.clear(); + } + + @Override + public void clear() { + behaviorStateChangeLatch = null; + clearDropMessagesToBehavior(); + messagesReceivedLatches.clear(); + super.clear(); + } + + void forwardCapturedMessageToBehavior(Class msgClass, ActorRef sender) throws Exception { + Object message = getFirstMatching(getSelf(), msgClass); + assertNotNull("Message of type " + msgClass + " not received", message); + getSelf().tell(message, sender); + } + + void forwardCapturedMessagesToBehavior(Class msgClass, ActorRef sender) throws Exception { + for(Object m: getAllMatching(getSelf(), msgClass)) { + getSelf().tell(m, sender); + } + } + + T getCapturedMessage(Class msgClass) throws Exception { + Object message = getFirstMatching(getSelf(), msgClass); + assertNotNull("Message of type " + msgClass + " not received", message); + return (T) message; + } + } + + static { + System.setProperty(SimpleLogger.LOG_KEY_PREFIX + MockRaftActorContext.class.getName(), "trace"); + } + + private final Logger testLog = LoggerFactory.getLogger(MockRaftActorContext.class); + private final ActorSystem system = ActorSystem.create("test"); + + @After + public void tearDown() { + JavaTestKit.shutdownActorSystem(system); + } + + private DefaultConfigParamsImpl newConfigParams() { + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + configParams.setHeartBeatInterval(new FiniteDuration(HEARTBEAT_INTERVAL, TimeUnit.MILLISECONDS)); + configParams.setElectionTimeoutFactor(100000); + configParams.setIsolatedLeaderCheckInterval(new FiniteDuration(1, TimeUnit.DAYS)); + return configParams; + } + + private MockRaftActorContext newRaftActorContext(String id, ActorRef actor, + Map peerAddresses) { + MockRaftActorContext context = new MockRaftActorContext(id, system, actor); + context.setPeerAddresses(peerAddresses); + context.getTermInformation().updateAndPersist(1, ""); + return context; + } + + private void verifyBehaviorState(String name, TestActorRef actor, RaftState expState) { + assertEquals(name + " behavior state", expState, actor.underlyingActor().behavior.state()); + } + + private void initializeLeaderBehavior(TestActorRef actor, RaftActorContext context, + int numActiveFollowers) throws Exception { + // Leader sends immediate heartbeats - we don't care about it so ignore it. + + actor.underlyingActor().expectMessageClass(AppendEntriesReply.class, numActiveFollowers); + Leader leader = new Leader(context); + actor.underlyingActor().waitForExpectedMessages(AppendEntriesReply.class); + actor.underlyingActor().behavior = leader; + + actor.underlyingActor().forwardCapturedMessagesToBehavior(AppendEntriesReply.class, ActorRef.noSender()); + actor.underlyingActor().clear(); + } + + private TestActorRef newMemberActor(String name) throws Exception { + TestActorRef actor = TestActorRef.create(system, MemberActor.props(), name); + MessageCollectorActor.waitUntilReady(actor); + return actor; + } + + private void sendHeartbeat(TestActorRef leaderActor) { + Uninterruptibles.sleepUninterruptibly(HEARTBEAT_INTERVAL, TimeUnit.MILLISECONDS); + leaderActor.underlyingActor().behavior.handleMessage(leaderActor, new SendHeartBeat()); + } + + @Test + public void testDelayedMessagesScenario() throws Exception { + testLog.info("Starting testDelayedMessagesScenario"); + + TestActorRef member1Actor = newMemberActor("member1"); + TestActorRef member2Actor = newMemberActor("member2"); + TestActorRef member3Actor = newMemberActor("member3"); + + // Create member 2's behavior initially as Follower + + MockRaftActorContext member2Context = newRaftActorContext("member2", member2Actor, + ImmutableMap.builder(). + put("member1", member1Actor.path().toString()). + put("member3", member3Actor.path().toString()).build()); + + DefaultConfigParamsImpl member2ConfigParams = newConfigParams(); + member2Context.setConfigParams(member2ConfigParams); + + Follower member2Behavior = new Follower(member2Context); + member2Actor.underlyingActor().behavior = member2Behavior; + + // Create member 3's behavior initially as Follower + + MockRaftActorContext member3Context = newRaftActorContext("member3", member3Actor, + ImmutableMap.builder(). + put("member1", member1Actor.path().toString()). + put("member2", member2Actor.path().toString()).build()); + + DefaultConfigParamsImpl member3ConfigParams = newConfigParams(); + member3Context.setConfigParams(member3ConfigParams); + + Follower member3Behavior = new Follower(member3Context); + member3Actor.underlyingActor().behavior = member3Behavior; + + // Create member 1's behavior initially as Leader + + MockRaftActorContext member1Context = newRaftActorContext("member1", member1Actor, + ImmutableMap.builder(). + put("member2", member2Actor.path().toString()). + put("member3", member3Actor.path().toString()).build()); + + DefaultConfigParamsImpl member1ConfigParams = newConfigParams(); + member1Context.setConfigParams(member1ConfigParams); + + initializeLeaderBehavior(member1Actor, member1Context, 2); + + member2Actor.underlyingActor().clear(); + member3Actor.underlyingActor().clear(); + + // Send ElectionTimeout to member 2 to simulate missing heartbeat from the Leader. member 2 + // should switch to Candidate and send out RequestVote messages. Set member 1 and 3 actors + // to capture RequestVote but not to forward to the behavior just yet as we want to + // control the order of RequestVote messages to member 1 and 3. + + member1Actor.underlyingActor().dropMessagesToBehavior(RequestVote.class); + + member2Actor.underlyingActor().expectBehaviorStateChange(); + + member3Actor.underlyingActor().dropMessagesToBehavior(RequestVote.class); + + member2Actor.tell(new ElectionTimeout(), ActorRef.noSender()); + + member1Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + member3Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + + member2Actor.underlyingActor().waitForBehaviorStateChange(); + verifyBehaviorState("member 2", member2Actor, RaftState.Candidate); + + assertEquals("member 1 election term", 1, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 2, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", 1, member3Context.getTermInformation().getCurrentTerm()); + + // At this point member 1 and 3 actors have captured the RequestVote messages. First + // forward the RequestVote message to member 1's behavior. Since the RequestVote term + // is greater than member 1's term, member 1 should switch to Follower without replying + // to RequestVote and update its term to 2. + + member1Actor.underlyingActor().clearDropMessagesToBehavior(); + member1Actor.underlyingActor().expectBehaviorStateChange(); + member1Actor.underlyingActor().forwardCapturedMessageToBehavior(RequestVote.class, member2Actor); + member1Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + + member1Actor.underlyingActor().waitForBehaviorStateChange(); + verifyBehaviorState("member 1", member1Actor, RaftState.Follower); + + // Now forward member 3's captured RequestVote message to its behavior. Since member 3 is + // already a Follower, it should update its term to 2 and send a RequestVoteReply back to + // member 2 granting the vote b/c the RequestVote's term, lastLogTerm, and lastLogIndex + // should satisfy the criteria for granting the vote. However, we'll delay sending the + // RequestVoteReply to member 2's behavior to simulate network latency. + + member2Actor.underlyingActor().dropMessagesToBehavior(RequestVoteReply.class); + + member3Actor.underlyingActor().clearDropMessagesToBehavior(); + member3Actor.underlyingActor().expectMessageClass(RequestVote.class, 1); + member3Actor.underlyingActor().forwardCapturedMessageToBehavior(RequestVote.class, member2Actor); + member3Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + verifyBehaviorState("member 3", member3Actor, RaftState.Follower); + + assertEquals("member 1 election term", 2, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 2, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", 2, member3Context.getTermInformation().getCurrentTerm()); + + // Send ElectionTimeout to member 3 to simulate missing heartbeat from a Leader. member 3 + // should switch to Candidate and send out RequestVote messages. member 1 should grant the + // vote and send a reply. After receiving the RequestVoteReply, member 3 should switch to leader. + + member2Actor.underlyingActor().expectBehaviorStateChange(); + member3Actor.underlyingActor().clear(); + member3Actor.underlyingActor().expectMessageClass(RequestVoteReply.class, 1); + member3Actor.underlyingActor().expectMessageClass(AppendEntriesReply.class, 2); + + member3Actor.tell(new ElectionTimeout(), ActorRef.noSender()); + + member3Actor.underlyingActor().waitForExpectedMessages(RequestVoteReply.class); + + RequestVoteReply requestVoteReply = member3Actor.underlyingActor().getCapturedMessage(RequestVoteReply.class); + assertEquals("getTerm", member3Context.getTermInformation().getCurrentTerm(), requestVoteReply.getTerm()); + assertEquals("isVoteGranted", true, requestVoteReply.isVoteGranted()); + + verifyBehaviorState("member 3", member3Actor, RaftState.Leader); + + // member 2 should've switched to Follower as member 3's RequestVote term (3) was greater + // than member 2's term (2). + + member2Actor.underlyingActor().waitForBehaviorStateChange(); + verifyBehaviorState("member 2", member2Actor, RaftState.Follower); + + // The switch to leader should cause an immediate AppendEntries heartbeat from member 3. + + member3Actor.underlyingActor().waitForExpectedMessages(AppendEntriesReply.class); + + assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", 3, member3Context.getTermInformation().getCurrentTerm()); + + // Now forward the original delayed RequestVoteReply from member 3 to member 2 that granted + // the vote. Since member 2 is now a Follower, the RequestVoteReply should be ignored. + + member2Actor.underlyingActor().clearDropMessagesToBehavior(); + member2Actor.underlyingActor().forwardCapturedMessageToBehavior(RequestVoteReply.class, member3Actor); + + member2Actor.underlyingActor().waitForExpectedMessages(RequestVoteReply.class); + + verifyBehaviorState("member 1", member1Actor, RaftState.Follower); + verifyBehaviorState("member 2", member2Actor, RaftState.Follower); + verifyBehaviorState("member 3", member3Actor, RaftState.Leader); + + assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", 3, member3Context.getTermInformation().getCurrentTerm()); + + testLog.info("testDelayedMessagesScenario done"); + } + + @Test + public void testPartitionedLeadersScenario() throws Exception { + testLog.info("Starting testPartitionedLeadersScenario"); + + TestActorRef member1Actor = newMemberActor("member1"); + TestActorRef member2Actor = newMemberActor("member2"); + TestActorRef member3Actor = newMemberActor("member3"); + + // Create member 2's behavior initially as Follower + + MockRaftActorContext member2Context = newRaftActorContext("member2", member2Actor, + ImmutableMap.builder(). + put("member1", member1Actor.path().toString()). + put("member3", member3Actor.path().toString()).build()); + + DefaultConfigParamsImpl member2ConfigParams = newConfigParams(); + member2Context.setConfigParams(member2ConfigParams); + + Follower member2Behavior = new Follower(member2Context); + member2Actor.underlyingActor().behavior = member2Behavior; + + // Create member 3's behavior initially as Follower + + MockRaftActorContext member3Context = newRaftActorContext("member3", member3Actor, + ImmutableMap.builder(). + put("member1", member1Actor.path().toString()). + put("member2", member2Actor.path().toString()).build()); + + DefaultConfigParamsImpl member3ConfigParams = newConfigParams(); + member3Context.setConfigParams(member3ConfigParams); + + Follower member3Behavior = new Follower(member3Context); + member3Actor.underlyingActor().behavior = member3Behavior; + + // Create member 1's behavior initially as Leader + + MockRaftActorContext member1Context = newRaftActorContext("member1", member1Actor, + ImmutableMap.builder(). + put("member2", member2Actor.path().toString()). + put("member3", member3Actor.path().toString()).build()); + + DefaultConfigParamsImpl member1ConfigParams = newConfigParams(); + member1Context.setConfigParams(member1ConfigParams); + + initializeLeaderBehavior(member1Actor, member1Context, 2); + + member2Actor.underlyingActor().clear(); + member3Actor.underlyingActor().clear(); + + // Send ElectionTimeout to member 2 to simulate no heartbeat from the Leader (member 1). + // member 2 should switch to Candidate, start new term 2 and send out RequestVote messages. + // member 1 will switch to Follower b/c its term is less than the RequestVote term, also it + // won't send back a reply. member 3 will drop the message (ie won't forward it to its behavior) to + // simulate loss of network connectivity between member 2 and 3. + + member1Actor.underlyingActor().expectMessageClass(RequestVote.class, 1); + + member2Actor.underlyingActor().expectBehaviorStateChange(); + + member3Actor.underlyingActor().dropMessagesToBehavior(RequestVote.class); + + member2Actor.tell(new ElectionTimeout(), ActorRef.noSender()); + + member1Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + member3Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + + // member 1 should switch to Follower as the RequestVote term is greater than its term. It + // won't send back a RequestVoteReply in this case. + + verifyBehaviorState("member 1", member1Actor, RaftState.Follower); + + // member 2 should switch to Candidate since member 1 didn't reply. + + member2Actor.underlyingActor().waitForBehaviorStateChange(); + verifyBehaviorState("member 2", member2Actor, RaftState.Candidate); + + assertEquals("member 1 election term", 2, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 2, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", 1, member3Context.getTermInformation().getCurrentTerm()); + + // Send ElectionTimeout to member 3 to simulate no heartbeat from the Leader (member 1). + // member 2 should switch to Candidate and send out RequestVote messages. member 1 will reply and + // grant the vote but member 2 will drop the message to simulate loss of network connectivity. + + member1Actor.underlyingActor().clear(); + member1Actor.underlyingActor().expectMessageClass(RequestVote.class, 1); + member1Actor.underlyingActor().expectMessageClass(AppendEntries.class, 1); + + member2Actor.underlyingActor().clear(); + member2Actor.underlyingActor().dropMessagesToBehavior(RequestVote.class); + member2Actor.underlyingActor().dropMessagesToBehavior(AppendEntries.class); + + member3Actor.underlyingActor().clear(); + member3Actor.underlyingActor().expectMessageClass(RequestVoteReply.class, 1); + member3Actor.underlyingActor().expectMessageClass(AppendEntriesReply.class, 1); + + member3Actor.tell(new ElectionTimeout(), ActorRef.noSender()); + + member1Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + member2Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + member3Actor.underlyingActor().waitForExpectedMessages(RequestVoteReply.class); + + RequestVoteReply requestVoteReply = member3Actor.underlyingActor().getCapturedMessage(RequestVoteReply.class); + assertEquals("getTerm", member3Context.getTermInformation().getCurrentTerm(), requestVoteReply.getTerm()); + assertEquals("isVoteGranted", true, requestVoteReply.isVoteGranted()); + + // when member 3 switches to Leader it will immediately send out heartbeat AppendEntries to + // the followers. Wait for AppendEntries to member 1 and its AppendEntriesReply. The + // AppendEntries message to member 2 is dropped. + + member1Actor.underlyingActor().waitForExpectedMessages(AppendEntries.class); + member2Actor.underlyingActor().waitForExpectedMessages(AppendEntries.class); + member3Actor.underlyingActor().waitForExpectedMessages(AppendEntriesReply.class); + + verifyBehaviorState("member 1", member1Actor, RaftState.Follower); + verifyBehaviorState("member 2", member2Actor, RaftState.Candidate); + verifyBehaviorState("member 3", member3Actor, RaftState.Leader); + + assertEquals("member 1 election term", 2, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 2, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", 2, member3Context.getTermInformation().getCurrentTerm()); + + // member 2 is partitioned from the Leader (member 3) and hasn't received any messages. It + // would get another ElectionTimeout so simulate that. member 1 should send back a reply + // granting the vote. Messages (RequestVote and AppendEntries) from member 2 to member 3 + // are dropped to simulate loss of network connectivity. Note member 2 will increment its + // election term to 3. + + member1Actor.underlyingActor().clear(); + member1Actor.underlyingActor().expectMessageClass(AppendEntries.class, 1); + + member2Actor.underlyingActor().clear(); + member2Actor.underlyingActor().expectMessageClass(RequestVoteReply.class, 1); + member2Actor.underlyingActor().expectMessageClass(AppendEntriesReply.class, 1); + + member3Actor.underlyingActor().clear(); + member3Actor.underlyingActor().dropMessagesToBehavior(AppendEntries.class); + member3Actor.underlyingActor().dropMessagesToBehavior(RequestVote.class); + + member2Actor.tell(new ElectionTimeout(), ActorRef.noSender()); + + member2Actor.underlyingActor().waitForExpectedMessages(RequestVoteReply.class); + + requestVoteReply = member2Actor.underlyingActor().getCapturedMessage(RequestVoteReply.class); + assertEquals("getTerm", member2Context.getTermInformation().getCurrentTerm(), requestVoteReply.getTerm()); + assertEquals("isVoteGranted", true, requestVoteReply.isVoteGranted()); + + member3Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + + member1Actor.underlyingActor().waitForExpectedMessages(AppendEntries.class); + member3Actor.underlyingActor().waitForExpectedMessages(AppendEntries.class); + member2Actor.underlyingActor().waitForExpectedMessages(AppendEntriesReply.class); + + // We end up with 2 partitioned leaders both leading member 1. The term for member 1 and 3 + // is 3 and member 3's term is 2. + + verifyBehaviorState("member 1", member1Actor, RaftState.Follower); + verifyBehaviorState("member 2", member2Actor, RaftState.Leader); + verifyBehaviorState("member 3", member3Actor, RaftState.Leader); + + assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", 2, member3Context.getTermInformation().getCurrentTerm()); + + // Re-establish connectivity between member 2 and 3, ie stop dropping messages between + // the 2. Send heartbeats (AppendEntries) from member 3. Both member 1 and 2 should send back + // an unsuccessful AppendEntriesReply b/c their term (3) is greater than member 3's term (2). + // This should cause member 3 to switch to Follower. + + RaftActorBehavior savedMember1Behavior = member1Actor.underlyingActor().behavior; + RaftActorBehavior savedMember2Behavior = member2Actor.underlyingActor().behavior; + RaftActorBehavior savedMember3Behavior = member3Actor.underlyingActor().behavior; + long savedMember3Term = member3Context.getTermInformation().getCurrentTerm(); + String savedMember3VoterFor = member3Context.getTermInformation().getVotedFor(); + + member1Actor.underlyingActor().clear(); + member1Actor.underlyingActor().expectMessageClass(AppendEntries.class, 1); + + member2Actor.underlyingActor().clear(); + member2Actor.underlyingActor().expectMessageClass(AppendEntries.class, 1); + + member3Actor.underlyingActor().clear(); + member3Actor.underlyingActor().expectMessageClass(AppendEntriesReply.class, 1); + + sendHeartbeat(member3Actor); + + member3Actor.underlyingActor().waitForExpectedMessages(AppendEntriesReply.class); + + AppendEntriesReply appendEntriesReply = member3Actor.underlyingActor(). + getCapturedMessage(AppendEntriesReply.class); + assertEquals("isSuccess", false, appendEntriesReply.isSuccess()); + assertEquals("getTerm", 3, appendEntriesReply.getTerm()); + + verifyBehaviorState("member 1", member1Actor, RaftState.Follower); + verifyBehaviorState("member 2", member2Actor, RaftState.Leader); + verifyBehaviorState("member 3", member3Actor, RaftState.Follower); + + assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", 3, member3Context.getTermInformation().getCurrentTerm()); + + // Revert back to the partitioned leaders state to test the other sequence where member 2 + // sends heartbeats first before member 3. member 1 should return a successful + // AppendEntriesReply b/c his term matches member 2's. member 3 should switch to Follower + // as his term is less than member 2's. + + member1Actor.underlyingActor().behavior = savedMember1Behavior; + member2Actor.underlyingActor().behavior = savedMember2Behavior; + member3Actor.underlyingActor().behavior = savedMember3Behavior; + + member3Context.getTermInformation().update(savedMember3Term, savedMember3VoterFor); + + member1Actor.underlyingActor().clear(); + member1Actor.underlyingActor().expectMessageClass(AppendEntries.class, 1); + + member2Actor.underlyingActor().clear(); + member2Actor.underlyingActor().expectMessageClass(AppendEntriesReply.class, 1); + + member3Actor.underlyingActor().clear(); + member3Actor.underlyingActor().expectMessageClass(AppendEntries.class, 1); + + sendHeartbeat(member2Actor); + + member1Actor.underlyingActor().waitForExpectedMessages(AppendEntries.class); + member3Actor.underlyingActor().waitForExpectedMessages(AppendEntries.class); + + member2Actor.underlyingActor().waitForExpectedMessages(AppendEntriesReply.class); + + verifyBehaviorState("member 1", member1Actor, RaftState.Follower); + verifyBehaviorState("member 2", member2Actor, RaftState.Leader); + verifyBehaviorState("member 3", member3Actor, RaftState.Follower); + + assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", 3, member3Context.getTermInformation().getCurrentTerm()); + + testLog.info("testPartitionedLeadersScenario done"); + } + + @Test + public void testPartitionedCandidateOnStartupScenario() throws Exception { + testLog.info("Starting testPartitionedCandidateOnStartupScenario"); + + TestActorRef member1Actor = newMemberActor("member1") ; + TestActorRef member2Actor = newMemberActor("member2"); + TestActorRef member3Actor = newMemberActor("member3"); + + // Create member 2's behavior as Follower. + + MockRaftActorContext member2Context = newRaftActorContext("member2", member2Actor, + ImmutableMap.builder(). + put("member1", member1Actor.path().toString()). + put("member3", member3Actor.path().toString()).build()); + + DefaultConfigParamsImpl member2ConfigParams = newConfigParams(); + member2Context.setConfigParams(member2ConfigParams); + + Follower member2Behavior = new Follower(member2Context); + member2Actor.underlyingActor().behavior = member2Behavior; + + // Create member 1's behavior as Leader. + + MockRaftActorContext member1Context = newRaftActorContext("member1", member1Actor, + ImmutableMap.builder(). + put("member2", member2Actor.path().toString()). + put("member3", member3Actor.path().toString()).build()); + + DefaultConfigParamsImpl member1ConfigParams = newConfigParams(); + member1Context.setConfigParams(member1ConfigParams); + + initializeLeaderBehavior(member1Actor, member1Context, 1); + + member2Actor.underlyingActor().clear(); + member3Actor.underlyingActor().clear(); + + // Initialize the ReplicatedLog and election term info for member 1 and 2. The current term + // will be 3 and the last term will be 2. + + SimpleReplicatedLog replicatedLog = new SimpleReplicatedLog(); + replicatedLog.append(new MockReplicatedLogEntry(2, 1, new MockPayload(""))); + replicatedLog.append(new MockReplicatedLogEntry(3, 1, new MockPayload(""))); + + member1Context.setReplicatedLog(replicatedLog); + member1Context.getTermInformation().update(3, ""); + + member2Context.setReplicatedLog(replicatedLog); + member2Context.getTermInformation().update(3, member1Context.getId()); + + // Create member 3's behavior initially as a Candidate. + + MockRaftActorContext member3Context = newRaftActorContext("member3", member3Actor, + ImmutableMap.builder(). + put("member1", member1Actor.path().toString()). + put("member2", member2Actor.path().toString()).build()); + + DefaultConfigParamsImpl member3ConfigParams = newConfigParams(); + member3Context.setConfigParams(member3ConfigParams); + + // Initialize the ReplicatedLog and election term info for Candidate member 3. The current term + // will be 2 and the last term will be 1 so it is behind the leader's log. + + SimpleReplicatedLog candidateReplicatedLog = new SimpleReplicatedLog(); + candidateReplicatedLog.append(new MockReplicatedLogEntry(1, 1, new MockPayload(""))); + candidateReplicatedLog.append(new MockReplicatedLogEntry(2, 1, new MockPayload(""))); + + member3Context.setReplicatedLog(candidateReplicatedLog); + member3Context.getTermInformation().update(2, member1Context.getId()); + + // The member 3 Candidate will start a new term and send RequestVotes. However it will be + // partitioned from the cluster by having member 1 and 2 drop its RequestVote messages. + + int numCandidateElections = 5; + long candidateElectionTerm = member3Context.getTermInformation().getCurrentTerm() + numCandidateElections; + + member1Actor.underlyingActor().dropMessagesToBehavior(RequestVote.class, numCandidateElections); + + member2Actor.underlyingActor().dropMessagesToBehavior(RequestVote.class, numCandidateElections); + + Candidate member3Behavior = new Candidate(member3Context); + member3Actor.underlyingActor().behavior = member3Behavior; + + // Send several additional ElectionTimeouts to Candidate member 3. Each ElectionTimeout will + // start a new term so Candidate member 3's current term will be greater than the leader's + // current term. + + for(int i = 0; i < numCandidateElections - 1; i++) { + member3Actor.tell(new ElectionTimeout(), ActorRef.noSender()); + } + + member1Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + member2Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + + verifyBehaviorState("member 1", member1Actor, RaftState.Leader); + verifyBehaviorState("member 2", member2Actor, RaftState.Follower); + verifyBehaviorState("member 3", member3Actor, RaftState.Candidate); + + assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", candidateElectionTerm, + member3Context.getTermInformation().getCurrentTerm()); + + // Now send a couple more ElectionTimeouts to Candidate member 3 with the partition resolved. + // + // On the first RequestVote, Leader member 1 should switch to Follower as its term (s) is less than + // the RequestVote's term (8) from member 3. No RequestVoteReply should be sent by member 1. + // Follower member 2 should update its term since it less than the RequestVote's term and + // should return a RequestVoteReply but should not grant the vote as its last term and index + // is greater than the RequestVote's lastLogTerm and lastLogIndex, ie member 2's log is later + // or more up to date than member 3's. + // + // On the second RequestVote, both member 1 and 2 are followers so they should update their + // term and return a RequestVoteReply but should not grant the vote. + + candidateElectionTerm += 2; + for(int i = 0; i < 2; i++) { + member1Actor.underlyingActor().clear(); + member1Actor.underlyingActor().expectMessageClass(RequestVote.class, 1); + member2Actor.underlyingActor().clear(); + member2Actor.underlyingActor().expectMessageClass(RequestVote.class, 1); + member3Actor.underlyingActor().clear(); + member3Actor.underlyingActor().expectMessageClass(RequestVoteReply.class, 1); + + member3Actor.tell(new ElectionTimeout(), ActorRef.noSender()); + + member1Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + member2Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + + member3Actor.underlyingActor().waitForExpectedMessages(RequestVoteReply.class); + + RequestVoteReply requestVoteReply = member3Actor.underlyingActor().getCapturedMessage(RequestVoteReply.class); + assertEquals("getTerm", member3Context.getTermInformation().getCurrentTerm(), requestVoteReply.getTerm()); + assertEquals("isVoteGranted", false, requestVoteReply.isVoteGranted()); + } + + verifyBehaviorState("member 1", member1Actor, RaftState.Follower); + verifyBehaviorState("member 2", member2Actor, RaftState.Follower); + verifyBehaviorState("member 3", member3Actor, RaftState.Candidate); + + // Even though member 3 didn't get voted for, member 1 and 2 should have updated their term + // to member 3's. + + assertEquals("member 1 election term", candidateElectionTerm, + member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", candidateElectionTerm, + member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", candidateElectionTerm, + member3Context.getTermInformation().getCurrentTerm()); + + // At this point we have no leader. Candidate member 3 would continue to start new elections + // but wouldn't be granted a vote. One of the 2 followers would eventually time out from + // not having received a heartbeat from a leader and switch to candidate and start a new + // election. We'll simulate that here by sending an ElectionTimeout to member 1. + + member1Actor.underlyingActor().clear(); + member1Actor.underlyingActor().expectMessageClass(RequestVoteReply.class, 1); + member2Actor.underlyingActor().clear(); + member2Actor.underlyingActor().expectMessageClass(RequestVote.class, 1); + member3Actor.underlyingActor().clear(); + member3Actor.underlyingActor().expectMessageClass(RequestVote.class, 1); + member3Actor.underlyingActor().expectBehaviorStateChange(); + + member1Actor.tell(new ElectionTimeout(), ActorRef.noSender()); + + member2Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + member3Actor.underlyingActor().waitForExpectedMessages(RequestVote.class); + + // The RequestVoteReply should come from Follower member 2 and the vote should be granted + // since member 2's last term and index matches member 1's. + + member1Actor.underlyingActor().waitForExpectedMessages(RequestVoteReply.class); + + RequestVoteReply requestVoteReply = member1Actor.underlyingActor().getCapturedMessage(RequestVoteReply.class); + assertEquals("getTerm", member1Context.getTermInformation().getCurrentTerm(), requestVoteReply.getTerm()); + assertEquals("isVoteGranted", true, requestVoteReply.isVoteGranted()); + + // Candidate member 3 should change to follower as its term should be less than the + // RequestVote term (member 1 started a new term higher than the other member's terms). + + member3Actor.underlyingActor().waitForBehaviorStateChange(); + + verifyBehaviorState("member 1", member1Actor, RaftState.Leader); + verifyBehaviorState("member 2", member2Actor, RaftState.Follower); + verifyBehaviorState("member 3", member3Actor, RaftState.Follower); + + // newTerm should be 10. + + long newTerm = candidateElectionTerm + 1; + assertEquals("member 1 election term", newTerm, member1Context.getTermInformation().getCurrentTerm()); + assertEquals("member 2 election term", newTerm, member2Context.getTermInformation().getCurrentTerm()); + assertEquals("member 3 election term", newTerm, member3Context.getTermInformation().getCurrentTerm()); + + testLog.info("testPartitionedCandidateOnStartupScenario done"); + } +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderTest.java index b31cb621b3..c57fce1cd5 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/LeaderTest.java @@ -10,15 +10,14 @@ import akka.actor.Terminated; import akka.testkit.JavaTestKit; import akka.testkit.TestActorRef; import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Uninterruptibles; import com.google.protobuf.ByteString; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; @@ -31,706 +30,657 @@ import org.opendaylight.controller.cluster.raft.SerializationUtils; import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries; import org.opendaylight.controller.cluster.raft.base.messages.ApplyState; import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot; -import org.opendaylight.controller.cluster.raft.base.messages.InitiateInstallSnapshot; import org.opendaylight.controller.cluster.raft.base.messages.IsolatedLeaderCheck; import org.opendaylight.controller.cluster.raft.base.messages.Replicate; import org.opendaylight.controller.cluster.raft.base.messages.SendHeartBeat; import org.opendaylight.controller.cluster.raft.base.messages.SendInstallSnapshot; +import org.opendaylight.controller.cluster.raft.behaviors.AbstractLeader.FollowerToSnapshot; import org.opendaylight.controller.cluster.raft.messages.AppendEntries; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; import org.opendaylight.controller.cluster.raft.messages.InstallSnapshot; import org.opendaylight.controller.cluster.raft.messages.InstallSnapshotReply; +import org.opendaylight.controller.cluster.raft.messages.RaftRPC; import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; -import org.opendaylight.controller.cluster.raft.utils.DoNothingActor; +import org.opendaylight.controller.cluster.raft.utils.ForwardMessageToBehaviorActor; import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; import org.opendaylight.controller.protobuff.messages.cluster.raft.InstallSnapshotMessages; import scala.concurrent.duration.FiniteDuration; -public class LeaderTest extends AbstractRaftActorBehaviorTest { +public class LeaderTest extends AbstractLeaderTest { - private final ActorRef leaderActor = - getSystem().actorOf(Props.create(DoNothingActor.class)); - private final ActorRef senderActor = - getSystem().actorOf(Props.create(DoNothingActor.class)); + static final String FOLLOWER_ID = "follower"; + + private final TestActorRef leaderActor = actorFactory.createTestActor( + Props.create(ForwardMessageToBehaviorActor.class), actorFactory.generateActorId("leader")); + + private final TestActorRef followerActor = actorFactory.createTestActor( + Props.create(ForwardMessageToBehaviorActor.class), actorFactory.generateActorId("follower")); + + private Leader leader; + + @Override + @After + public void tearDown() throws Exception { + if(leader != null) { + leader.close(); + } + + super.tearDown(); + } @Test public void testHandleMessageForUnknownMessage() throws Exception { - new JavaTestKit(getSystem()) {{ - Leader leader = - new Leader(createActorContext()); + logStart("testHandleMessageForUnknownMessage"); - // handle message should return the Leader state when it receives an - // unknown message - RaftActorBehavior behavior = leader.handleMessage(senderActor, "foo"); - Assert.assertTrue(behavior instanceof Leader); - }}; + leader = new Leader(createActorContext()); + + // handle message should return the Leader state when it receives an + // unknown message + RaftActorBehavior behavior = leader.handleMessage(followerActor, "foo"); + Assert.assertTrue(behavior instanceof Leader); } @Test - public void testThatLeaderSendsAHeartbeatMessageToAllFollowers() { - new JavaTestKit(getSystem()) {{ + public void testThatLeaderSendsAHeartbeatMessageToAllFollowers() throws Exception { + logStart("testThatLeaderSendsAHeartbeatMessageToAllFollowers"); - new Within(duration("1 seconds")) { - @Override - protected void run() { + MockRaftActorContext actorContext = createActorContextWithFollower(); - ActorRef followerActor = getTestActor(); + long term = 1; + actorContext.getTermInformation().update(term, ""); - MockRaftActorContext actorContext = (MockRaftActorContext) createActorContext(); + leader = new Leader(actorContext); - Map peerAddresses = new HashMap<>(); + // Leader should send an immediate heartbeat with no entries as follower is inactive. + long lastIndex = actorContext.getReplicatedLog().lastIndex(); + AppendEntries appendEntries = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); + assertEquals("getTerm", term, appendEntries.getTerm()); + assertEquals("getPrevLogIndex", -1, appendEntries.getPrevLogIndex()); + assertEquals("getPrevLogTerm", -1, appendEntries.getPrevLogTerm()); + assertEquals("Entries size", 0, appendEntries.getEntries().size()); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + // The follower would normally reply - simulate that explicitly here. + leader.handleMessage(followerActor, new AppendEntriesReply( + FOLLOWER_ID, term, true, lastIndex - 1, term)); + assertEquals("isFollowerActive", true, leader.getFollower(FOLLOWER_ID).isFollowerActive()); - actorContext.setPeerAddresses(peerAddresses); + followerActor.underlyingActor().clear(); - Leader leader = new Leader(actorContext); - leader.handleMessage(senderActor, new SendHeartBeat()); + // Sleep for the heartbeat interval so AppendEntries is sent. + Uninterruptibles.sleepUninterruptibly(actorContext.getConfigParams(). + getHeartBeatInterval().toMillis(), TimeUnit.MILLISECONDS); - final String out = - new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - Object msg = fromSerializableMessage(in); - if (msg instanceof AppendEntries) { - if (((AppendEntries)msg).getTerm() == 0) { - return "match"; - } - return null; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message + leader.handleMessage(leaderActor, new SendHeartBeat()); - assertEquals("match", out); - - } - }; - }}; + appendEntries = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); + assertEquals("getPrevLogIndex", lastIndex - 1, appendEntries.getPrevLogIndex()); + assertEquals("getPrevLogTerm", term, appendEntries.getPrevLogTerm()); + assertEquals("Entries size", 1, appendEntries.getEntries().size()); + assertEquals("Entry getIndex", lastIndex, appendEntries.getEntries().get(0).getIndex()); + assertEquals("Entry getTerm", term, appendEntries.getEntries().get(0).getTerm()); } @Test - public void testHandleReplicateMessageSendAppendEntriesToFollower() { - new JavaTestKit(getSystem()) {{ + public void testHandleReplicateMessageSendAppendEntriesToFollower() throws Exception { + logStart("testHandleReplicateMessageSendAppendEntriesToFollower"); - new Within(duration("1 seconds")) { - @Override - protected void run() { - - ActorRef followerActor = getTestActor(); - - MockRaftActorContext actorContext = - (MockRaftActorContext) createActorContext(); - - Map peerAddresses = new HashMap<>(); - - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); - - actorContext.setPeerAddresses(peerAddresses); - - Leader leader = new Leader(actorContext); - RaftActorBehavior raftBehavior = leader - .handleMessage(senderActor, new Replicate(null, null, - new MockRaftActorContext.MockReplicatedLogEntry(1, - 100, - new MockRaftActorContext.MockPayload("foo")) - )); - - // State should not change - assertTrue(raftBehavior instanceof Leader); - - final String out = - new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - Object msg = fromSerializableMessage(in); - if (msg instanceof AppendEntries) { - if (((AppendEntries)msg).getTerm() == 0) { - return "match"; - } - return null; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); - } - }; - }}; + MockRaftActorContext actorContext = createActorContextWithFollower(); + + long term = 1; + actorContext.getTermInformation().update(term, ""); + + leader = new Leader(actorContext); + + // Leader will send an immediate heartbeat - ignore it. + MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); + + // The follower would normally reply - simulate that explicitly here. + long lastIndex = actorContext.getReplicatedLog().lastIndex(); + leader.handleMessage(followerActor, new AppendEntriesReply( + FOLLOWER_ID, term, true, lastIndex, term)); + assertEquals("isFollowerActive", true, leader.getFollower(FOLLOWER_ID).isFollowerActive()); + + followerActor.underlyingActor().clear(); + + MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("foo"); + MockRaftActorContext.MockReplicatedLogEntry newEntry = new MockRaftActorContext.MockReplicatedLogEntry( + 1, lastIndex + 1, payload); + actorContext.getReplicatedLog().append(newEntry); + RaftActorBehavior raftBehavior = leader.handleMessage(leaderActor, + new Replicate(null, null, newEntry)); + + // State should not change + assertTrue(raftBehavior instanceof Leader); + + AppendEntries appendEntries = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); + assertEquals("getPrevLogIndex", lastIndex, appendEntries.getPrevLogIndex()); + assertEquals("getPrevLogTerm", term, appendEntries.getPrevLogTerm()); + assertEquals("Entries size", 1, appendEntries.getEntries().size()); + assertEquals("Entry getIndex", lastIndex + 1, appendEntries.getEntries().get(0).getIndex()); + assertEquals("Entry getTerm", term, appendEntries.getEntries().get(0).getTerm()); + assertEquals("Entry payload", payload, appendEntries.getEntries().get(0).getData()); } @Test - public void testHandleReplicateMessageWhenThereAreNoFollowers() { - new JavaTestKit(getSystem()) {{ + public void testHandleReplicateMessageWhenThereAreNoFollowers() throws Exception { + logStart("testHandleReplicateMessageWhenThereAreNoFollowers"); - new Within(duration("1 seconds")) { - @Override - protected void run() { + MockRaftActorContext actorContext = createActorContext(); - ActorRef raftActor = getTestActor(); + leader = new Leader(actorContext); - MockRaftActorContext actorContext = - new MockRaftActorContext("test", getSystem(), raftActor); + actorContext.setLastApplied(0); - actorContext.getReplicatedLog().removeFrom(0); + long newLogIndex = actorContext.getReplicatedLog().lastIndex() + 1; + long term = actorContext.getTermInformation().getCurrentTerm(); + MockRaftActorContext.MockReplicatedLogEntry newEntry = new MockRaftActorContext.MockReplicatedLogEntry( + term, newLogIndex, new MockRaftActorContext.MockPayload("foo")); - actorContext.setReplicatedLog( - new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1) - .build()); + actorContext.getReplicatedLog().append(newEntry); - Leader leader = new Leader(actorContext); - RaftActorBehavior raftBehavior = leader - .handleMessage(senderActor, new Replicate(null, "state-id",actorContext.getReplicatedLog().get(1))); + RaftActorBehavior raftBehavior = leader.handleMessage(leaderActor, + new Replicate(leaderActor, "state-id", newEntry)); - // State should not change - assertTrue(raftBehavior instanceof Leader); + // State should not change + assertTrue(raftBehavior instanceof Leader); - assertEquals(1, actorContext.getCommitIndex()); + assertEquals("getCommitIndex", newLogIndex, actorContext.getCommitIndex()); - final String out = - new ExpectMsg(duration("1 seconds"), - "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in instanceof ApplyState) { - if (((ApplyState) in).getIdentifier().equals("state-id")) { - return "match"; - } - return null; - } else { - throw noMatch(); - } - } - }.get(); // this extracts the received message + // We should get 2 ApplyState messages - 1 for new log entry and 1 for the previous + // one since lastApplied state is 0. + List applyStateList = MessageCollectorActor.getAllMatching( + leaderActor, ApplyState.class); + assertEquals("ApplyState count", newLogIndex, applyStateList.size()); - assertEquals("match", out); + for(int i = 0; i <= newLogIndex - 1; i++ ) { + ApplyState applyState = applyStateList.get(i); + assertEquals("getIndex", i + 1, applyState.getReplicatedLogEntry().getIndex()); + assertEquals("getTerm", term, applyState.getReplicatedLogEntry().getTerm()); + } - } - }; - }}; + ApplyState last = applyStateList.get((int) newLogIndex - 1); + assertEquals("getData", newEntry.getData(), last.getReplicatedLogEntry().getData()); + assertEquals("getIdentifier", "state-id", last.getIdentifier()); } @Test public void testSendAppendEntriesOnAnInProgressInstallSnapshot() throws Exception { - new JavaTestKit(getSystem()) {{ - ActorRef followerActor = getSystem().actorOf(Props.create(MessageCollectorActor.class)); + logStart("testSendAppendEntriesOnAnInProgressInstallSnapshot"); - Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); - - MockRaftActorContext actorContext = - (MockRaftActorContext) createActorContext(leaderActor); - actorContext.setPeerAddresses(peerAddresses); - - Map leadersSnapshot = new HashMap<>(); - leadersSnapshot.put("1", "A"); - leadersSnapshot.put("2", "B"); - leadersSnapshot.put("3", "C"); - - //clears leaders log - actorContext.getReplicatedLog().removeFrom(0); - - final int followersLastIndex = 2; - final int snapshotIndex = 3; - final int newEntryIndex = 4; - final int snapshotTerm = 1; - final int currentTerm = 2; - - // set the snapshot variables in replicatedlog - actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); - actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); - actorContext.setCommitIndex(followersLastIndex); - //set follower timeout to 2 mins, helps during debugging - actorContext.setConfigParams(new MockConfigParamsImpl(120000L, 10)); - - MockLeader leader = new MockLeader(actorContext); - - // new entry - ReplicatedLogImplEntry entry = - new ReplicatedLogImplEntry(newEntryIndex, currentTerm, - new MockRaftActorContext.MockPayload("D")); + MockRaftActorContext actorContext = createActorContextWithFollower(); - //update follower timestamp - leader.markFollowerActive(followerActor.path().toString()); + Map leadersSnapshot = new HashMap<>(); + leadersSnapshot.put("1", "A"); + leadersSnapshot.put("2", "B"); + leadersSnapshot.put("3", "C"); + + //clears leaders log + actorContext.getReplicatedLog().removeFrom(0); + + final int followersLastIndex = 2; + final int snapshotIndex = 3; + final int newEntryIndex = 4; + final int snapshotTerm = 1; + final int currentTerm = 2; - ByteString bs = toByteString(leadersSnapshot); - leader.setSnapshot(Optional.of(bs)); - leader.createFollowerToSnapshot(followerActor.path().toString(), bs); + // set the snapshot variables in replicatedlog + actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); + actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); + actorContext.setCommitIndex(followersLastIndex); + //set follower timeout to 2 mins, helps during debugging + actorContext.setConfigParams(new MockConfigParamsImpl(120000L, 10)); - //send first chunk and no InstallSnapshotReply received yet - leader.getFollowerToSnapshot().getNextChunk(); - leader.getFollowerToSnapshot().incrementChunkIndex(); + leader = new Leader(actorContext); - leader.handleMessage(leaderActor, new SendHeartBeat()); + // new entry + ReplicatedLogImplEntry entry = + new ReplicatedLogImplEntry(newEntryIndex, currentTerm, + new MockRaftActorContext.MockPayload("D")); + + //update follower timestamp + leader.markFollowerActive(FOLLOWER_ID); - AppendEntries aeproto = (AppendEntries)MessageCollectorActor.getFirstMatching( - followerActor, AppendEntries.class); + ByteString bs = toByteString(leadersSnapshot); + leader.setSnapshot(Optional.of(bs)); + FollowerToSnapshot fts = leader.new FollowerToSnapshot(bs); + leader.setFollowerSnapshot(FOLLOWER_ID, fts); - assertNotNull("AppendEntries should be sent even if InstallSnapshotReply is not " + - "received", aeproto); + //send first chunk and no InstallSnapshotReply received yet + fts.getNextChunk(); + fts.incrementChunkIndex(); - AppendEntries ae = (AppendEntries) SerializationUtils.fromSerializable(aeproto); + Uninterruptibles.sleepUninterruptibly(actorContext.getConfigParams().getHeartBeatInterval().toMillis(), + TimeUnit.MILLISECONDS); - assertTrue("AppendEntries should be sent with empty entries", ae.getEntries().isEmpty()); + leader.handleMessage(leaderActor, new SendHeartBeat()); - //InstallSnapshotReply received - leader.getFollowerToSnapshot().markSendStatus(true); + AppendEntries aeproto = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); - leader.handleMessage(senderActor, new SendHeartBeat()); + AppendEntries ae = (AppendEntries) SerializationUtils.fromSerializable(aeproto); - InstallSnapshotMessages.InstallSnapshot isproto = (InstallSnapshotMessages.InstallSnapshot) - MessageCollectorActor.getFirstMatching(followerActor, - InstallSnapshot.SERIALIZABLE_CLASS); + assertTrue("AppendEntries should be sent with empty entries", ae.getEntries().isEmpty()); - assertNotNull("Installsnapshot should get called for sending the next chunk of snapshot", - isproto); + //InstallSnapshotReply received + fts.markSendStatus(true); - InstallSnapshot is = (InstallSnapshot) SerializationUtils.fromSerializable(isproto); + leader.handleMessage(leaderActor, new SendHeartBeat()); - assertEquals(snapshotIndex, is.getLastIncludedIndex()); + InstallSnapshotMessages.InstallSnapshot isproto = MessageCollectorActor.expectFirstMatching(followerActor, + InstallSnapshot.SERIALIZABLE_CLASS); - }}; + InstallSnapshot is = (InstallSnapshot) SerializationUtils.fromSerializable(isproto); + + assertEquals(snapshotIndex, is.getLastIncludedIndex()); } @Test - public void testSendAppendEntriesSnapshotScenario() { - new JavaTestKit(getSystem()) {{ + public void testSendAppendEntriesSnapshotScenario() throws Exception { + logStart("testSendAppendEntriesSnapshotScenario"); - ActorRef followerActor = getTestActor(); - - Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + MockRaftActorContext actorContext = createActorContextWithFollower(); - MockRaftActorContext actorContext = - (MockRaftActorContext) createActorContext(getRef()); - actorContext.setPeerAddresses(peerAddresses); + Map leadersSnapshot = new HashMap<>(); + leadersSnapshot.put("1", "A"); + leadersSnapshot.put("2", "B"); + leadersSnapshot.put("3", "C"); - Map leadersSnapshot = new HashMap<>(); - leadersSnapshot.put("1", "A"); - leadersSnapshot.put("2", "B"); - leadersSnapshot.put("3", "C"); + //clears leaders log + actorContext.getReplicatedLog().removeFrom(0); - //clears leaders log - actorContext.getReplicatedLog().removeFrom(0); + final int followersLastIndex = 2; + final int snapshotIndex = 3; + final int newEntryIndex = 4; + final int snapshotTerm = 1; + final int currentTerm = 2; - final int followersLastIndex = 2; - final int snapshotIndex = 3; - final int newEntryIndex = 4; - final int snapshotTerm = 1; - final int currentTerm = 2; + // set the snapshot variables in replicatedlog + actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); + actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); + actorContext.setCommitIndex(followersLastIndex); - // set the snapshot variables in replicatedlog - actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); - actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); - actorContext.setCommitIndex(followersLastIndex); + leader = new Leader(actorContext); - Leader leader = new Leader(actorContext); + // Leader will send an immediate heartbeat - ignore it. + MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); - // new entry - ReplicatedLogImplEntry entry = + // new entry + ReplicatedLogImplEntry entry = new ReplicatedLogImplEntry(newEntryIndex, currentTerm, - new MockRaftActorContext.MockPayload("D")); - - //update follower timestamp - leader.markFollowerActive(followerActor.path().toString()); - - // this should invoke a sendinstallsnapshot as followersLastIndex < snapshotIndex - RaftActorBehavior raftBehavior = leader.handleMessage( - senderActor, new Replicate(null, "state-id", entry)); - - assertTrue(raftBehavior instanceof Leader); - - // we might receive some heartbeat messages, so wait till we InitiateInstallSnapshot - Boolean[] matches = new ReceiveWhile(Boolean.class, duration("2 seconds")) { - @Override - protected Boolean match(Object o) throws Exception { - if (o instanceof InitiateInstallSnapshot) { - return true; - } - return false; - } - }.get(); - - boolean initiateInitiateInstallSnapshot = false; - for (Boolean b: matches) { - initiateInitiateInstallSnapshot = b | initiateInitiateInstallSnapshot; - } + new MockRaftActorContext.MockPayload("D")); - assertTrue(initiateInitiateInstallSnapshot); - }}; + //update follower timestamp + leader.markFollowerActive(FOLLOWER_ID); + + // this should invoke a sendinstallsnapshot as followersLastIndex < snapshotIndex + RaftActorBehavior raftBehavior = leader.handleMessage( + leaderActor, new Replicate(null, "state-id", entry)); + + assertTrue(raftBehavior instanceof Leader); + + MessageCollectorActor.expectFirstMatching(leaderActor, CaptureSnapshot.class); } @Test public void testInitiateInstallSnapshot() throws Exception { - new JavaTestKit(getSystem()) {{ + logStart("testInitiateInstallSnapshot"); - ActorRef leaderActor = getSystem().actorOf(Props.create(MessageCollectorActor.class)); + MockRaftActorContext actorContext = createActorContextWithFollower(); - ActorRef followerActor = getTestActor(); + Map leadersSnapshot = new HashMap<>(); + leadersSnapshot.put("1", "A"); + leadersSnapshot.put("2", "B"); + leadersSnapshot.put("3", "C"); - Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + //clears leaders log + actorContext.getReplicatedLog().removeFrom(0); + final int followersLastIndex = 2; + final int snapshotIndex = 3; + final int newEntryIndex = 4; + final int snapshotTerm = 1; + final int currentTerm = 2; - MockRaftActorContext actorContext = - (MockRaftActorContext) createActorContext(leaderActor); - actorContext.setPeerAddresses(peerAddresses); + // set the snapshot variables in replicatedlog + actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); + actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); + actorContext.setLastApplied(3); + actorContext.setCommitIndex(followersLastIndex); - Map leadersSnapshot = new HashMap<>(); - leadersSnapshot.put("1", "A"); - leadersSnapshot.put("2", "B"); - leadersSnapshot.put("3", "C"); + leader = new Leader(actorContext); - //clears leaders log - actorContext.getReplicatedLog().removeFrom(0); + // Leader will send an immediate heartbeat - ignore it. + MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); - final int followersLastIndex = 2; - final int snapshotIndex = 3; - final int newEntryIndex = 4; - final int snapshotTerm = 1; - final int currentTerm = 2; + // set the snapshot as absent and check if capture-snapshot is invoked. + leader.setSnapshot(Optional.absent()); - // set the snapshot variables in replicatedlog - actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); - actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); - actorContext.setLastApplied(3); - actorContext.setCommitIndex(followersLastIndex); + // new entry + ReplicatedLogImplEntry entry = new ReplicatedLogImplEntry(newEntryIndex, currentTerm, + new MockRaftActorContext.MockPayload("D")); - Leader leader = new Leader(actorContext); - // set the snapshot as absent and check if capture-snapshot is invoked. - leader.setSnapshot(Optional.absent()); + actorContext.getReplicatedLog().append(entry); - // new entry - ReplicatedLogImplEntry entry = - new ReplicatedLogImplEntry(newEntryIndex, currentTerm, - new MockRaftActorContext.MockPayload("D")); + //update follower timestamp + leader.markFollowerActive(FOLLOWER_ID); - actorContext.getReplicatedLog().append(entry); + leader.handleMessage(leaderActor, new Replicate(null, "state-id", entry)); - // this should invoke a sendinstallsnapshot as followersLastIndex < snapshotIndex - RaftActorBehavior raftBehavior = leader.handleMessage( - leaderActor, new InitiateInstallSnapshot()); + CaptureSnapshot cs = MessageCollectorActor.expectFirstMatching(leaderActor, CaptureSnapshot.class); - CaptureSnapshot cs = (CaptureSnapshot) MessageCollectorActor. - getFirstMatching(leaderActor, CaptureSnapshot.class); + assertTrue(cs.isInstallSnapshotInitiated()); + assertEquals(3, cs.getLastAppliedIndex()); + assertEquals(1, cs.getLastAppliedTerm()); + assertEquals(4, cs.getLastIndex()); + assertEquals(2, cs.getLastTerm()); - assertNotNull(cs); + // if an initiate is started again when first is in progress, it shouldnt initiate Capture + leader.handleMessage(leaderActor, new Replicate(null, "state-id", entry)); - assertTrue(cs.isInstallSnapshotInitiated()); - assertEquals(3, cs.getLastAppliedIndex()); - assertEquals(1, cs.getLastAppliedTerm()); - assertEquals(4, cs.getLastIndex()); - assertEquals(2, cs.getLastTerm()); - }}; + List captureSnapshots = MessageCollectorActor.getAllMatching(leaderActor, CaptureSnapshot.class); + assertEquals("CaptureSnapshot should not get invoked when initiate is in progress", 1, captureSnapshots.size()); } @Test - public void testInstallSnapshot() { - new JavaTestKit(getSystem()) {{ + public void testInstallSnapshot() throws Exception { + logStart("testInstallSnapshot"); - ActorRef followerActor = getTestActor(); + MockRaftActorContext actorContext = createActorContextWithFollower(); - Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + Map leadersSnapshot = new HashMap<>(); + leadersSnapshot.put("1", "A"); + leadersSnapshot.put("2", "B"); + leadersSnapshot.put("3", "C"); - MockRaftActorContext actorContext = - (MockRaftActorContext) createActorContext(); - actorContext.setPeerAddresses(peerAddresses); + //clears leaders log + actorContext.getReplicatedLog().removeFrom(0); + final int followersLastIndex = 2; + final int snapshotIndex = 3; + final int snapshotTerm = 1; + final int currentTerm = 2; - Map leadersSnapshot = new HashMap<>(); - leadersSnapshot.put("1", "A"); - leadersSnapshot.put("2", "B"); - leadersSnapshot.put("3", "C"); + // set the snapshot variables in replicatedlog + actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); + actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); + actorContext.getTermInformation().update(currentTerm, leaderActor.path().toString()); + actorContext.setCommitIndex(followersLastIndex); - //clears leaders log - actorContext.getReplicatedLog().removeFrom(0); + leader = new Leader(actorContext); - final int followersLastIndex = 2; - final int snapshotIndex = 3; - final int newEntryIndex = 4; - final int snapshotTerm = 1; - final int currentTerm = 2; + // Ignore initial heartbeat. + MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); - // set the snapshot variables in replicatedlog - actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); - actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); - actorContext.getTermInformation().update(currentTerm, leaderActor.path().toString()); - actorContext.setCommitIndex(followersLastIndex); + RaftActorBehavior raftBehavior = leader.handleMessage(leaderActor, + new SendInstallSnapshot(toByteString(leadersSnapshot))); - Leader leader = new Leader(actorContext); + assertTrue(raftBehavior instanceof Leader); - // new entry - ReplicatedLogImplEntry entry = - new ReplicatedLogImplEntry(newEntryIndex, currentTerm, - new MockRaftActorContext.MockPayload("D")); + // check if installsnapshot gets called with the correct values. - RaftActorBehavior raftBehavior = leader.handleMessage(senderActor, - new SendInstallSnapshot(toByteString(leadersSnapshot))); + InstallSnapshot installSnapshot = (InstallSnapshot) SerializationUtils.fromSerializable( + MessageCollectorActor.expectFirstMatching(followerActor, InstallSnapshotMessages.InstallSnapshot.class)); - assertTrue(raftBehavior instanceof Leader); - - // check if installsnapshot gets called with the correct values. - final String out = - new ExpectMsg(duration("1 seconds"), "match hint") { - // do not put code outside this method, will run afterwards - @Override - protected String match(Object in) { - if (in instanceof InstallSnapshotMessages.InstallSnapshot) { - InstallSnapshot is = (InstallSnapshot) - SerializationUtils.fromSerializable(in); - if (is.getData() == null) { - return "InstallSnapshot data is null"; - } - if (is.getLastIncludedIndex() != snapshotIndex) { - return is.getLastIncludedIndex() + "!=" + snapshotIndex; - } - if (is.getLastIncludedTerm() != snapshotTerm) { - return is.getLastIncludedTerm() + "!=" + snapshotTerm; - } - if (is.getTerm() == currentTerm) { - return is.getTerm() + "!=" + currentTerm; - } - - return "match"; - - } else { - return "message mismatch:" + in.getClass(); - } - } - }.get(); // this extracts the received message - - assertEquals("match", out); - }}; + assertNotNull(installSnapshot.getData()); + assertEquals(snapshotIndex, installSnapshot.getLastIncludedIndex()); + assertEquals(snapshotTerm, installSnapshot.getLastIncludedTerm()); + + assertEquals(currentTerm, installSnapshot.getTerm()); } @Test - public void testHandleInstallSnapshotReplyLastChunk() { - new JavaTestKit(getSystem()) {{ + public void testHandleInstallSnapshotReplyLastChunk() throws Exception { + logStart("testHandleInstallSnapshotReplyLastChunk"); - ActorRef followerActor = getTestActor(); + MockRaftActorContext actorContext = createActorContextWithFollower(); - Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); - - final int followersLastIndex = 2; - final int snapshotIndex = 3; - final int newEntryIndex = 4; - final int snapshotTerm = 1; - final int currentTerm = 2; - - MockRaftActorContext actorContext = - (MockRaftActorContext) createActorContext(); - actorContext.setPeerAddresses(peerAddresses); - actorContext.setCommitIndex(followersLastIndex); - - MockLeader leader = new MockLeader(actorContext); - - Map leadersSnapshot = new HashMap<>(); - leadersSnapshot.put("1", "A"); - leadersSnapshot.put("2", "B"); - leadersSnapshot.put("3", "C"); - - // set the snapshot variables in replicatedlog - - actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); - actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); - actorContext.getTermInformation().update(currentTerm, leaderActor.path().toString()); - - ByteString bs = toByteString(leadersSnapshot); - leader.setSnapshot(Optional.of(bs)); - leader.createFollowerToSnapshot(followerActor.path().toString(), bs); - while(!leader.getFollowerToSnapshot().isLastChunk(leader.getFollowerToSnapshot().getChunkIndex())) { - leader.getFollowerToSnapshot().getNextChunk(); - leader.getFollowerToSnapshot().incrementChunkIndex(); - } + final int followersLastIndex = 2; + final int snapshotIndex = 3; + final int snapshotTerm = 1; + final int currentTerm = 2; - //clears leaders log - actorContext.getReplicatedLog().removeFrom(0); + actorContext.setCommitIndex(followersLastIndex); - RaftActorBehavior raftBehavior = leader.handleMessage(senderActor, - new InstallSnapshotReply(currentTerm, followerActor.path().toString(), - leader.getFollowerToSnapshot().getChunkIndex(), true)); + leader = new Leader(actorContext); - assertTrue(raftBehavior instanceof Leader); + // Ignore initial heartbeat. + MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); - assertEquals(0, leader.followerSnapshotSize()); - assertEquals(1, leader.followerLogSize()); - assertNotNull(leader.getFollower(followerActor.path().toString())); - FollowerLogInformation fli = leader.getFollower(followerActor.path().toString()); - assertEquals(snapshotIndex, fli.getMatchIndex()); - assertEquals(snapshotIndex, fli.getMatchIndex()); - assertEquals(snapshotIndex + 1, fli.getNextIndex()); - }}; - } + Map leadersSnapshot = new HashMap<>(); + leadersSnapshot.put("1", "A"); + leadersSnapshot.put("2", "B"); + leadersSnapshot.put("3", "C"); - @Test - public void testHandleInstallSnapshotReplyWithInvalidChunkIndex() throws Exception { - new JavaTestKit(getSystem()) {{ + // set the snapshot variables in replicatedlog - TestActorRef followerActor = - TestActorRef.create(getSystem(), Props.create(MessageCollectorActor.class), "follower"); + actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); + actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); + actorContext.getTermInformation().update(currentTerm, leaderActor.path().toString()); - Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + ByteString bs = toByteString(leadersSnapshot); + leader.setSnapshot(Optional.of(bs)); + FollowerToSnapshot fts = leader.new FollowerToSnapshot(bs); + leader.setFollowerSnapshot(FOLLOWER_ID, fts); + while(!fts.isLastChunk(fts.getChunkIndex())) { + fts.getNextChunk(); + fts.incrementChunkIndex(); + } - final int followersLastIndex = 2; - final int snapshotIndex = 3; - final int snapshotTerm = 1; - final int currentTerm = 2; + //clears leaders log + actorContext.getReplicatedLog().removeFrom(0); - MockRaftActorContext actorContext = - (MockRaftActorContext) createActorContext(); + RaftActorBehavior raftBehavior = leader.handleMessage(followerActor, + new InstallSnapshotReply(currentTerm, FOLLOWER_ID, fts.getChunkIndex(), true)); - actorContext.setConfigParams(new DefaultConfigParamsImpl(){ - @Override - public int getSnapshotChunkSize() { - return 50; - } - }); - actorContext.setPeerAddresses(peerAddresses); - actorContext.setCommitIndex(followersLastIndex); + assertTrue(raftBehavior instanceof Leader); - MockLeader leader = new MockLeader(actorContext); + assertEquals(0, leader.followerSnapshotSize()); + assertEquals(1, leader.followerLogSize()); + FollowerLogInformation fli = leader.getFollower(FOLLOWER_ID); + assertNotNull(fli); + assertEquals(snapshotIndex, fli.getMatchIndex()); + assertEquals(snapshotIndex, fli.getMatchIndex()); + assertEquals(snapshotIndex + 1, fli.getNextIndex()); + } - Map leadersSnapshot = new HashMap<>(); - leadersSnapshot.put("1", "A"); - leadersSnapshot.put("2", "B"); - leadersSnapshot.put("3", "C"); + @Test + public void testSendSnapshotfromInstallSnapshotReply() throws Exception { + logStart("testSendSnapshotfromInstallSnapshotReply"); - // set the snapshot variables in replicatedlog - actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); - actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); - actorContext.getTermInformation().update(currentTerm, leaderActor.path().toString()); + MockRaftActorContext actorContext = createActorContextWithFollower(); - ByteString bs = toByteString(leadersSnapshot); - leader.setSnapshot(Optional.of(bs)); + final int followersLastIndex = 2; + final int snapshotIndex = 3; + final int snapshotTerm = 1; + final int currentTerm = 2; - leader.handleMessage(leaderActor, new SendInstallSnapshot(bs)); + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(){ + @Override + public int getSnapshotChunkSize() { + return 50; + } + }; + configParams.setHeartBeatInterval(new FiniteDuration(9, TimeUnit.SECONDS)); + configParams.setIsolatedLeaderCheckInterval(new FiniteDuration(10, TimeUnit.SECONDS)); - Object o = MessageCollectorActor.getAllMessages(followerActor).get(0); + actorContext.setConfigParams(configParams); + actorContext.setCommitIndex(followersLastIndex); - assertTrue(o instanceof InstallSnapshotMessages.InstallSnapshot); + leader = new Leader(actorContext); - InstallSnapshotMessages.InstallSnapshot installSnapshot = (InstallSnapshotMessages.InstallSnapshot) o; + Map leadersSnapshot = new HashMap<>(); + leadersSnapshot.put("1", "A"); + leadersSnapshot.put("2", "B"); + leadersSnapshot.put("3", "C"); + + // set the snapshot variables in replicatedlog + actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); + actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); + actorContext.getTermInformation().update(currentTerm, leaderActor.path().toString()); - assertEquals(1, installSnapshot.getChunkIndex()); - assertEquals(3, installSnapshot.getTotalChunks()); + ByteString bs = toByteString(leadersSnapshot); + leader.setSnapshot(Optional.of(bs)); + leader.handleMessage(leaderActor, new SendInstallSnapshot(bs)); - leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), followerActor.path().toString(), -1, false)); + InstallSnapshotMessages.InstallSnapshot installSnapshot = MessageCollectorActor.expectFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); - leader.handleMessage(leaderActor, new SendHeartBeat()); + assertEquals(1, installSnapshot.getChunkIndex()); + assertEquals(3, installSnapshot.getTotalChunks()); - o = MessageCollectorActor.getAllMessages(followerActor).get(1); + followerActor.underlyingActor().clear(); + leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), + FOLLOWER_ID, installSnapshot.getChunkIndex(), true)); - assertTrue(o instanceof InstallSnapshotMessages.InstallSnapshot); + installSnapshot = MessageCollectorActor.expectFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); - installSnapshot = (InstallSnapshotMessages.InstallSnapshot) o; + assertEquals(2, installSnapshot.getChunkIndex()); + assertEquals(3, installSnapshot.getTotalChunks()); - assertEquals(1, installSnapshot.getChunkIndex()); - assertEquals(3, installSnapshot.getTotalChunks()); + followerActor.underlyingActor().clear(); + leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), + FOLLOWER_ID, installSnapshot.getChunkIndex(), true)); - followerActor.tell(PoisonPill.getInstance(), getRef()); - }}; + installSnapshot = MessageCollectorActor.expectFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); + + // Send snapshot reply one more time and make sure that a new snapshot message should not be sent to follower + followerActor.underlyingActor().clear(); + leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), + FOLLOWER_ID, installSnapshot.getChunkIndex(), true)); + + installSnapshot = MessageCollectorActor.getFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); + + Assert.assertNull(installSnapshot); } + @Test - public void testHandleSnapshotSendsPreviousChunksHashCodeWhenSendingNextChunk() throws Exception { - new JavaTestKit(getSystem()) { - { + public void testHandleInstallSnapshotReplyWithInvalidChunkIndex() throws Exception{ + logStart("testHandleInstallSnapshotReplyWithInvalidChunkIndex"); - TestActorRef followerActor = - TestActorRef.create(getSystem(), Props.create(MessageCollectorActor.class), "follower"); + MockRaftActorContext actorContext = createActorContextWithFollower(); - Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + final int followersLastIndex = 2; + final int snapshotIndex = 3; + final int snapshotTerm = 1; + final int currentTerm = 2; - final int followersLastIndex = 2; - final int snapshotIndex = 3; - final int snapshotTerm = 1; - final int currentTerm = 2; + actorContext.setConfigParams(new DefaultConfigParamsImpl(){ + @Override + public int getSnapshotChunkSize() { + return 50; + } + }); - MockRaftActorContext actorContext = - (MockRaftActorContext) createActorContext(); + actorContext.setCommitIndex(followersLastIndex); - actorContext.setConfigParams(new DefaultConfigParamsImpl() { - @Override - public int getSnapshotChunkSize() { - return 50; - } - }); - actorContext.setPeerAddresses(peerAddresses); - actorContext.setCommitIndex(followersLastIndex); + leader = new Leader(actorContext); - MockLeader leader = new MockLeader(actorContext); + Map leadersSnapshot = new HashMap<>(); + leadersSnapshot.put("1", "A"); + leadersSnapshot.put("2", "B"); + leadersSnapshot.put("3", "C"); - Map leadersSnapshot = new HashMap<>(); - leadersSnapshot.put("1", "A"); - leadersSnapshot.put("2", "B"); - leadersSnapshot.put("3", "C"); + // set the snapshot variables in replicatedlog + actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); + actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); + actorContext.getTermInformation().update(currentTerm, leaderActor.path().toString()); - // set the snapshot variables in replicatedlog - actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); - actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); - actorContext.getTermInformation().update(currentTerm, leaderActor.path().toString()); + ByteString bs = toByteString(leadersSnapshot); + leader.setSnapshot(Optional.of(bs)); - ByteString bs = toByteString(leadersSnapshot); - leader.setSnapshot(Optional.of(bs)); + leader.handleMessage(leaderActor, new SendInstallSnapshot(bs)); - leader.handleMessage(leaderActor, new SendInstallSnapshot(bs)); + InstallSnapshotMessages.InstallSnapshot installSnapshot = MessageCollectorActor.expectFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); - Object o = MessageCollectorActor.getAllMessages(followerActor).get(0); + assertEquals(1, installSnapshot.getChunkIndex()); + assertEquals(3, installSnapshot.getTotalChunks()); - assertTrue(o instanceof InstallSnapshotMessages.InstallSnapshot); + followerActor.underlyingActor().clear(); - InstallSnapshotMessages.InstallSnapshot installSnapshot = (InstallSnapshotMessages.InstallSnapshot) o; + leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), + FOLLOWER_ID, -1, false)); - assertEquals(1, installSnapshot.getChunkIndex()); - assertEquals(3, installSnapshot.getTotalChunks()); - assertEquals(AbstractLeader.INITIAL_LAST_CHUNK_HASH_CODE, installSnapshot.getLastChunkHashCode()); + Uninterruptibles.sleepUninterruptibly(actorContext.getConfigParams().getHeartBeatInterval().toMillis(), + TimeUnit.MILLISECONDS); - int hashCode = installSnapshot.getData().hashCode(); + leader.handleMessage(leaderActor, new SendHeartBeat()); - leader.handleMessage(followerActor, new InstallSnapshotReply(installSnapshot.getTerm(),followerActor.path().toString(),1,true )); + installSnapshot = MessageCollectorActor.expectFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); - leader.handleMessage(leaderActor, new SendHeartBeat()); + assertEquals(1, installSnapshot.getChunkIndex()); + assertEquals(3, installSnapshot.getTotalChunks()); + } - Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); + @Test + public void testHandleSnapshotSendsPreviousChunksHashCodeWhenSendingNextChunk() throws Exception { + logStart("testHandleSnapshotSendsPreviousChunksHashCodeWhenSendingNextChunk"); - o = MessageCollectorActor.getAllMessages(followerActor).get(1); + MockRaftActorContext actorContext = createActorContextWithFollower(); - assertTrue(o instanceof InstallSnapshotMessages.InstallSnapshot); + final int followersLastIndex = 2; + final int snapshotIndex = 3; + final int snapshotTerm = 1; + final int currentTerm = 2; - installSnapshot = (InstallSnapshotMessages.InstallSnapshot) o; + actorContext.setConfigParams(new DefaultConfigParamsImpl() { + @Override + public int getSnapshotChunkSize() { + return 50; + } + }); - assertEquals(2, installSnapshot.getChunkIndex()); - assertEquals(3, installSnapshot.getTotalChunks()); - assertEquals(hashCode, installSnapshot.getLastChunkHashCode()); + actorContext.setCommitIndex(followersLastIndex); - followerActor.tell(PoisonPill.getInstance(), getRef()); - }}; + leader = new Leader(actorContext); + + Map leadersSnapshot = new HashMap<>(); + leadersSnapshot.put("1", "A"); + leadersSnapshot.put("2", "B"); + leadersSnapshot.put("3", "C"); + + // set the snapshot variables in replicatedlog + actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex); + actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm); + actorContext.getTermInformation().update(currentTerm, leaderActor.path().toString()); + + ByteString bs = toByteString(leadersSnapshot); + leader.setSnapshot(Optional.of(bs)); + + leader.handleMessage(leaderActor, new SendInstallSnapshot(bs)); + + InstallSnapshotMessages.InstallSnapshot installSnapshot = MessageCollectorActor.expectFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); + + assertEquals(1, installSnapshot.getChunkIndex()); + assertEquals(3, installSnapshot.getTotalChunks()); + assertEquals(AbstractLeader.INITIAL_LAST_CHUNK_HASH_CODE, installSnapshot.getLastChunkHashCode()); + + int hashCode = installSnapshot.getData().hashCode(); + + followerActor.underlyingActor().clear(); + + leader.handleMessage(followerActor, new InstallSnapshotReply(installSnapshot.getTerm(), + FOLLOWER_ID, 1, true)); + + installSnapshot = MessageCollectorActor.expectFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); + + assertEquals(2, installSnapshot.getChunkIndex()); + assertEquals(3, installSnapshot.getTotalChunks()); + assertEquals(hashCode, installSnapshot.getLastChunkHashCode()); } @Test public void testFollowerToSnapshotLogic() { + logStart("testFollowerToSnapshotLogic"); - MockRaftActorContext actorContext = (MockRaftActorContext) createActorContext(); + MockRaftActorContext actorContext = createActorContext(); actorContext.setConfigParams(new DefaultConfigParamsImpl() { @Override @@ -739,7 +689,7 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { } }); - MockLeader leader = new MockLeader(actorContext); + leader = new Leader(actorContext); Map leadersSnapshot = new HashMap<>(); leadersSnapshot.put("1", "A"); @@ -749,7 +699,9 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { ByteString bs = toByteString(leadersSnapshot); byte[] barray = bs.toByteArray(); - leader.createFollowerToSnapshot("followerId", bs); + FollowerToSnapshot fts = leader.new FollowerToSnapshot(bs); + leader.setFollowerSnapshot(FOLLOWER_ID, fts); + assertEquals(bs.size(), barray.length); int chunkIndex=0; @@ -761,371 +713,294 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { j = barray.length; } - ByteString chunk = leader.getFollowerToSnapshot().getNextChunk(); + ByteString chunk = fts.getNextChunk(); assertEquals("bytestring size not matching for chunk:"+ chunkIndex, j-i, chunk.size()); - assertEquals("chunkindex not matching", chunkIndex, leader.getFollowerToSnapshot().getChunkIndex()); + assertEquals("chunkindex not matching", chunkIndex, fts.getChunkIndex()); - leader.getFollowerToSnapshot().markSendStatus(true); - if (!leader.getFollowerToSnapshot().isLastChunk(chunkIndex)) { - leader.getFollowerToSnapshot().incrementChunkIndex(); + fts.markSendStatus(true); + if (!fts.isLastChunk(chunkIndex)) { + fts.incrementChunkIndex(); } } - assertEquals("totalChunks not matching", chunkIndex, leader.getFollowerToSnapshot().getTotalChunks()); + assertEquals("totalChunks not matching", chunkIndex, fts.getTotalChunks()); } - @Override protected RaftActorBehavior createBehavior( RaftActorContext actorContext) { return new Leader(actorContext); } - @Override protected RaftActorContext createActorContext() { + @Override + protected MockRaftActorContext createActorContext() { return createActorContext(leaderActor); } @Override - protected RaftActorContext createActorContext(ActorRef actorRef) { - return new MockRaftActorContext("test", getSystem(), actorRef); + protected MockRaftActorContext createActorContext(ActorRef actorRef) { + return createActorContext("leader", actorRef); } - private ByteString toByteString(Map state) { - ByteArrayOutputStream b = null; - ObjectOutputStream o = null; - try { - try { - b = new ByteArrayOutputStream(); - o = new ObjectOutputStream(b); - o.writeObject(state); - byte[] snapshotBytes = b.toByteArray(); - return ByteString.copyFrom(snapshotBytes); - } finally { - if (o != null) { - o.flush(); - o.close(); - } - if (b != null) { - b.close(); - } - } - } catch (IOException e) { - Assert.fail("IOException in converting Hashmap to Bytestring:" + e); - } - return null; + private MockRaftActorContext createActorContextWithFollower() { + MockRaftActorContext actorContext = createActorContext(); + actorContext.setPeerAddresses(ImmutableMap.builder().put(FOLLOWER_ID, + followerActor.path().toString()).build()); + return actorContext; } - public static class ForwardMessageToBehaviorActor extends MessageCollectorActor { - private static AbstractRaftActorBehavior behavior; - - public ForwardMessageToBehaviorActor(){ - - } - - @Override public void onReceive(Object message) throws Exception { - super.onReceive(message); - behavior.handleMessage(sender(), message); - } - - public static void setBehavior(AbstractRaftActorBehavior behavior){ - ForwardMessageToBehaviorActor.behavior = behavior; - } + private MockRaftActorContext createActorContext(String id, ActorRef actorRef) { + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + configParams.setHeartBeatInterval(new FiniteDuration(50, TimeUnit.MILLISECONDS)); + configParams.setElectionTimeoutFactor(100000); + MockRaftActorContext context = new MockRaftActorContext(id, getSystem(), actorRef); + context.setConfigParams(configParams); + return context; } @Test public void testLeaderCreatedWithCommitIndexLessThanLastIndex() throws Exception { - new JavaTestKit(getSystem()) {{ - - ActorRef leaderActor = getSystem().actorOf(Props.create(MessageCollectorActor.class)); - - MockRaftActorContext leaderActorContext = - new MockRaftActorContext("leader", getSystem(), leaderActor); - - ActorRef followerActor = getSystem().actorOf(Props.create(ForwardMessageToBehaviorActor.class)); + logStart("testLeaderCreatedWithCommitIndexLessThanLastIndex"); - MockRaftActorContext followerActorContext = - new MockRaftActorContext("follower", getSystem(), followerActor); + MockRaftActorContext leaderActorContext = createActorContextWithFollower(); - Follower follower = new Follower(followerActorContext); + MockRaftActorContext followerActorContext = createActorContext(FOLLOWER_ID, followerActor); - ForwardMessageToBehaviorActor.setBehavior(follower); + Follower follower = new Follower(followerActorContext); + followerActor.underlyingActor().setBehavior(follower); - Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + Map peerAddresses = new HashMap<>(); + peerAddresses.put(FOLLOWER_ID, followerActor.path().toString()); - leaderActorContext.setPeerAddresses(peerAddresses); + leaderActorContext.setPeerAddresses(peerAddresses); - leaderActorContext.getReplicatedLog().removeFrom(0); + leaderActorContext.getReplicatedLog().removeFrom(0); - //create 3 entries - leaderActorContext.setReplicatedLog( + //create 3 entries + leaderActorContext.setReplicatedLog( new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); - leaderActorContext.setCommitIndex(1); + leaderActorContext.setCommitIndex(1); - followerActorContext.getReplicatedLog().removeFrom(0); + followerActorContext.getReplicatedLog().removeFrom(0); - // follower too has the exact same log entries and has the same commit index - followerActorContext.setReplicatedLog( + // follower too has the exact same log entries and has the same commit index + followerActorContext.setReplicatedLog( new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); - followerActorContext.setCommitIndex(1); - - Leader leader = new Leader(leaderActorContext); - leader.markFollowerActive(followerActor.path().toString()); + followerActorContext.setCommitIndex(1); - leader.handleMessage(leaderActor, new SendHeartBeat()); + leader = new Leader(leaderActorContext); - AppendEntries appendEntries = (AppendEntries) MessageCollectorActor - .getFirstMatching(followerActor, AppendEntries.class); + AppendEntries appendEntries = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); - assertNotNull(appendEntries); + assertEquals(1, appendEntries.getLeaderCommit()); + assertEquals(0, appendEntries.getEntries().size()); + assertEquals(0, appendEntries.getPrevLogIndex()); - assertEquals(1, appendEntries.getLeaderCommit()); - assertEquals(1, appendEntries.getEntries().get(0).getIndex()); - assertEquals(0, appendEntries.getPrevLogIndex()); + AppendEntriesReply appendEntriesReply = MessageCollectorActor.expectFirstMatching( + leaderActor, AppendEntriesReply.class); - AppendEntriesReply appendEntriesReply = - (AppendEntriesReply) MessageCollectorActor.getFirstMatching( - leaderActor, AppendEntriesReply.class); + assertEquals(2, appendEntriesReply.getLogLastIndex()); + assertEquals(1, appendEntriesReply.getLogLastTerm()); - assertNotNull(appendEntriesReply); + // follower returns its next index + assertEquals(2, appendEntriesReply.getLogLastIndex()); + assertEquals(1, appendEntriesReply.getLogLastTerm()); - // follower returns its next index - assertEquals(2, appendEntriesReply.getLogLastIndex()); - assertEquals(1, appendEntriesReply.getLogLastTerm()); - - }}; + follower.close(); } - @Test public void testLeaderCreatedWithCommitIndexLessThanFollowersCommitIndex() throws Exception { - new JavaTestKit(getSystem()) {{ + logStart("testLeaderCreatedWithCommitIndexLessThanFollowersCommitIndex"); - ActorRef leaderActor = getSystem().actorOf(Props.create(MessageCollectorActor.class)); + MockRaftActorContext leaderActorContext = createActorContext(); - MockRaftActorContext leaderActorContext = - new MockRaftActorContext("leader", getSystem(), leaderActor); + MockRaftActorContext followerActorContext = createActorContext(FOLLOWER_ID, followerActor); - ActorRef followerActor = getSystem().actorOf( - Props.create(ForwardMessageToBehaviorActor.class)); + Follower follower = new Follower(followerActorContext); + followerActor.underlyingActor().setBehavior(follower); - MockRaftActorContext followerActorContext = - new MockRaftActorContext("follower", getSystem(), followerActor); + Map peerAddresses = new HashMap<>(); + peerAddresses.put(FOLLOWER_ID, followerActor.path().toString()); - Follower follower = new Follower(followerActorContext); + leaderActorContext.setPeerAddresses(peerAddresses); - ForwardMessageToBehaviorActor.setBehavior(follower); + leaderActorContext.getReplicatedLog().removeFrom(0); - Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + leaderActorContext.setReplicatedLog( + new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); - leaderActorContext.setPeerAddresses(peerAddresses); + leaderActorContext.setCommitIndex(1); - leaderActorContext.getReplicatedLog().removeFrom(0); + followerActorContext.getReplicatedLog().removeFrom(0); - leaderActorContext.setReplicatedLog( + followerActorContext.setReplicatedLog( new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); - leaderActorContext.setCommitIndex(1); + // follower has the same log entries but its commit index > leaders commit index + followerActorContext.setCommitIndex(2); - followerActorContext.getReplicatedLog().removeFrom(0); + leader = new Leader(leaderActorContext); - followerActorContext.setReplicatedLog( - new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); + // Initial heartbeat + AppendEntries appendEntries = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); - // follower has the same log entries but its commit index > leaders commit index - followerActorContext.setCommitIndex(2); + assertEquals(1, appendEntries.getLeaderCommit()); + assertEquals(0, appendEntries.getEntries().size()); + assertEquals(0, appendEntries.getPrevLogIndex()); - Leader leader = new Leader(leaderActorContext); - leader.markFollowerActive(followerActor.path().toString()); + AppendEntriesReply appendEntriesReply = MessageCollectorActor.expectFirstMatching( + leaderActor, AppendEntriesReply.class); - leader.handleMessage(leaderActor, new SendHeartBeat()); + assertEquals(2, appendEntriesReply.getLogLastIndex()); + assertEquals(1, appendEntriesReply.getLogLastTerm()); - AppendEntries appendEntries = (AppendEntries) MessageCollectorActor - .getFirstMatching(followerActor, AppendEntries.class); + leaderActor.underlyingActor().setBehavior(follower); + leader.handleMessage(followerActor, appendEntriesReply); - assertNotNull(appendEntries); + leaderActor.underlyingActor().clear(); + followerActor.underlyingActor().clear(); - assertEquals(1, appendEntries.getLeaderCommit()); - assertEquals(1, appendEntries.getEntries().get(0).getIndex()); - assertEquals(0, appendEntries.getPrevLogIndex()); + Uninterruptibles.sleepUninterruptibly(leaderActorContext.getConfigParams().getHeartBeatInterval().toMillis(), + TimeUnit.MILLISECONDS); - AppendEntriesReply appendEntriesReply = - (AppendEntriesReply) MessageCollectorActor.getFirstMatching( - leaderActor, AppendEntriesReply.class); + leader.handleMessage(leaderActor, new SendHeartBeat()); - assertNotNull(appendEntriesReply); + appendEntries = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); - assertEquals(2, appendEntriesReply.getLogLastIndex()); - assertEquals(1, appendEntriesReply.getLogLastTerm()); + assertEquals(2, appendEntries.getLeaderCommit()); + assertEquals(0, appendEntries.getEntries().size()); + assertEquals(2, appendEntries.getPrevLogIndex()); - }}; - } + appendEntriesReply = MessageCollectorActor.expectFirstMatching(leaderActor, AppendEntriesReply.class); - @Test - public void testHandleAppendEntriesReplyFailure(){ - new JavaTestKit(getSystem()) { - { + assertEquals(2, appendEntriesReply.getLogLastIndex()); + assertEquals(1, appendEntriesReply.getLogLastTerm()); - ActorRef leaderActor = - getSystem().actorOf(Props.create(MessageCollectorActor.class)); + assertEquals(2, followerActorContext.getCommitIndex()); - ActorRef followerActor = - getSystem().actorOf(Props.create(MessageCollectorActor.class)); + follower.close(); + } + @Test + public void testHandleAppendEntriesReplyFailure(){ + logStart("testHandleAppendEntriesReplyFailure"); - MockRaftActorContext leaderActorContext = - new MockRaftActorContext("leader", getSystem(), leaderActor); + MockRaftActorContext leaderActorContext = createActorContextWithFollower(); - Map peerAddresses = new HashMap<>(); - peerAddresses.put("follower-1", - followerActor.path().toString()); + leader = new Leader(leaderActorContext); - leaderActorContext.setPeerAddresses(peerAddresses); + // Send initial heartbeat reply with last index. + leader.handleAppendEntriesReply(followerActor, new AppendEntriesReply(FOLLOWER_ID, 1, true, 10, 1)); - Leader leader = new Leader(leaderActorContext); + FollowerLogInformation followerInfo = leader.getFollower(FOLLOWER_ID); + assertEquals("getNextIndex", 11, followerInfo.getNextIndex()); - AppendEntriesReply reply = new AppendEntriesReply("follower-1", 1, false, 10, 1); + AppendEntriesReply reply = new AppendEntriesReply(FOLLOWER_ID, 1, false, 10, 1); - RaftActorBehavior raftActorBehavior = leader.handleAppendEntriesReply(followerActor, reply); + RaftActorBehavior raftActorBehavior = leader.handleAppendEntriesReply(followerActor, reply); - assertEquals(RaftState.Leader, raftActorBehavior.state()); + assertEquals(RaftState.Leader, raftActorBehavior.state()); - }}; + assertEquals("getNextIndex", 10, followerInfo.getNextIndex()); } @Test public void testHandleAppendEntriesReplySuccess() throws Exception { - new JavaTestKit(getSystem()) { - { - - ActorRef leaderActor = - getSystem().actorOf(Props.create(MessageCollectorActor.class)); - - ActorRef followerActor = - getSystem().actorOf(Props.create(MessageCollectorActor.class)); - - - MockRaftActorContext leaderActorContext = - new MockRaftActorContext("leader", getSystem(), leaderActor); - - leaderActorContext.setReplicatedLog( - new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); + logStart("testHandleAppendEntriesReplySuccess"); - Map peerAddresses = new HashMap<>(); - peerAddresses.put("follower-1", - followerActor.path().toString()); + MockRaftActorContext leaderActorContext = createActorContextWithFollower(); - leaderActorContext.setPeerAddresses(peerAddresses); - leaderActorContext.setCommitIndex(1); - leaderActorContext.setLastApplied(1); - leaderActorContext.getTermInformation().update(1, "leader"); - - Leader leader = new Leader(leaderActorContext); + leaderActorContext.setReplicatedLog( + new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); - AppendEntriesReply reply = new AppendEntriesReply("follower-1", 1, true, 2, 1); + leaderActorContext.setCommitIndex(1); + leaderActorContext.setLastApplied(1); + leaderActorContext.getTermInformation().update(1, "leader"); - RaftActorBehavior raftActorBehavior = leader.handleAppendEntriesReply(followerActor, reply); + leader = new Leader(leaderActorContext); - assertEquals(RaftState.Leader, raftActorBehavior.state()); + AppendEntriesReply reply = new AppendEntriesReply(FOLLOWER_ID, 1, true, 2, 1); - assertEquals(2, leaderActorContext.getCommitIndex()); + RaftActorBehavior raftActorBehavior = leader.handleAppendEntriesReply(followerActor, reply); - ApplyLogEntries applyLogEntries = - (ApplyLogEntries) MessageCollectorActor.getFirstMatching(leaderActor, - ApplyLogEntries.class); + assertEquals(RaftState.Leader, raftActorBehavior.state()); - assertNotNull(applyLogEntries); + assertEquals(2, leaderActorContext.getCommitIndex()); - assertEquals(2, leaderActorContext.getLastApplied()); + ApplyLogEntries applyLogEntries = MessageCollectorActor.expectFirstMatching( + leaderActor, ApplyLogEntries.class); - assertEquals(2, applyLogEntries.getToIndex()); + assertEquals(2, leaderActorContext.getLastApplied()); - List applyStateList = MessageCollectorActor.getAllMatching(leaderActor, - ApplyState.class); + assertEquals(2, applyLogEntries.getToIndex()); - assertEquals(1,applyStateList.size()); + List applyStateList = MessageCollectorActor.getAllMatching(leaderActor, + ApplyState.class); - ApplyState applyState = (ApplyState) applyStateList.get(0); + assertEquals(1,applyStateList.size()); - assertEquals(2, applyState.getReplicatedLogEntry().getIndex()); + ApplyState applyState = applyStateList.get(0); - }}; + assertEquals(2, applyState.getReplicatedLogEntry().getIndex()); } @Test public void testHandleAppendEntriesReplyUnknownFollower(){ - new JavaTestKit(getSystem()) { - { - - ActorRef leaderActor = - getSystem().actorOf(Props.create(MessageCollectorActor.class)); - - MockRaftActorContext leaderActorContext = - new MockRaftActorContext("leader", getSystem(), leaderActor); + logStart("testHandleAppendEntriesReplyUnknownFollower"); - Leader leader = new Leader(leaderActorContext); + MockRaftActorContext leaderActorContext = createActorContext(); - AppendEntriesReply reply = new AppendEntriesReply("follower-1", 1, false, 10, 1); + leader = new Leader(leaderActorContext); - RaftActorBehavior raftActorBehavior = leader.handleAppendEntriesReply(getRef(), reply); + AppendEntriesReply reply = new AppendEntriesReply("unkown-follower", 1, false, 10, 1); - assertEquals(RaftState.Leader, raftActorBehavior.state()); + RaftActorBehavior raftActorBehavior = leader.handleAppendEntriesReply(followerActor, reply); - }}; + assertEquals(RaftState.Leader, raftActorBehavior.state()); } @Test public void testHandleRequestVoteReply(){ - new JavaTestKit(getSystem()) { - { + logStart("testHandleRequestVoteReply"); - ActorRef leaderActor = - getSystem().actorOf(Props.create(MessageCollectorActor.class)); + MockRaftActorContext leaderActorContext = createActorContext(); - MockRaftActorContext leaderActorContext = - new MockRaftActorContext("leader", getSystem(), leaderActor); + leader = new Leader(leaderActorContext); - Leader leader = new Leader(leaderActorContext); + // Should be a no-op. + RaftActorBehavior raftActorBehavior = leader.handleRequestVoteReply(followerActor, + new RequestVoteReply(1, true)); - RaftActorBehavior raftActorBehavior = leader.handleRequestVoteReply(getRef(), new RequestVoteReply(1, true)); + assertEquals(RaftState.Leader, raftActorBehavior.state()); - assertEquals(RaftState.Leader, raftActorBehavior.state()); + raftActorBehavior = leader.handleRequestVoteReply(followerActor, new RequestVoteReply(1, false)); - raftActorBehavior = leader.handleRequestVoteReply(getRef(), new RequestVoteReply(1, false)); - - assertEquals(RaftState.Leader, raftActorBehavior.state()); - }}; + assertEquals(RaftState.Leader, raftActorBehavior.state()); } @Test public void testIsolatedLeaderCheckNoFollowers() { - new JavaTestKit(getSystem()) {{ - ActorRef leaderActor = getTestActor(); + logStart("testIsolatedLeaderCheckNoFollowers"); - MockRaftActorContext leaderActorContext = - new MockRaftActorContext("leader", getSystem(), leaderActor); + MockRaftActorContext leaderActorContext = createActorContext(); - Map peerAddresses = new HashMap<>(); - leaderActorContext.setPeerAddresses(peerAddresses); - - Leader leader = new Leader(leaderActorContext); - RaftActorBehavior behavior = leader.handleMessage(leaderActor, new IsolatedLeaderCheck()); - Assert.assertTrue(behavior instanceof Leader); - }}; + leader = new Leader(leaderActorContext); + RaftActorBehavior behavior = leader.handleMessage(leaderActor, new IsolatedLeaderCheck()); + Assert.assertTrue(behavior instanceof Leader); } @Test public void testIsolatedLeaderCheckTwoFollowers() throws Exception { + logStart("testIsolatedLeaderCheckTwoFollowers"); + new JavaTestKit(getSystem()) {{ ActorRef followerActor1 = getTestActor(); ActorRef followerActor2 = getTestActor(); - MockRaftActorContext leaderActorContext = (MockRaftActorContext) createActorContext(); + MockRaftActorContext leaderActorContext = createActorContext(); Map peerAddresses = new HashMap<>(); peerAddresses.put("follower-1", followerActor1.path().toString()); @@ -1133,8 +1008,7 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { leaderActorContext.setPeerAddresses(peerAddresses); - Leader leader = new Leader(leaderActorContext); - leader.stopIsolatedLeaderCheckSchedule(); + leader = new Leader(leaderActorContext); leader.markFollowerActive("follower-1"); leader.markFollowerActive("follower-2"); @@ -1166,26 +1040,150 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { behavior = leader.handleMessage(leaderActor, new IsolatedLeaderCheck()); Assert.assertTrue("Behavior not instance of IsolatedLeader when majority followers are inactive", behavior instanceof IsolatedLeader); - }}; } - class MockLeader extends Leader { - FollowerToSnapshot fts; + @Test + public void testAppendEntryCallAtEndofAppendEntryReply() throws Exception { + logStart("testAppendEntryCallAtEndofAppendEntryReply"); + + MockRaftActorContext leaderActorContext = createActorContextWithFollower(); - public MockLeader(RaftActorContext context){ - super(context); - } + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + //configParams.setHeartBeatInterval(new FiniteDuration(9, TimeUnit.SECONDS)); + configParams.setIsolatedLeaderCheckInterval(new FiniteDuration(10, TimeUnit.SECONDS)); - public FollowerToSnapshot getFollowerToSnapshot() { - return fts; - } + leaderActorContext.setConfigParams(configParams); - public void createFollowerToSnapshot(String followerId, ByteString bs ) { - fts = new FollowerToSnapshot(bs); - setFollowerSnapshot(followerId, fts); - } + MockRaftActorContext followerActorContext = createActorContext(FOLLOWER_ID, followerActor); + + followerActorContext.setConfigParams(configParams); + + Follower follower = new Follower(followerActorContext); + followerActor.underlyingActor().setBehavior(follower); + + leaderActorContext.getReplicatedLog().removeFrom(0); + leaderActorContext.setCommitIndex(-1); + leaderActorContext.setLastApplied(-1); + + followerActorContext.getReplicatedLog().removeFrom(0); + followerActorContext.setCommitIndex(-1); + followerActorContext.setLastApplied(-1); + + leader = new Leader(leaderActorContext); + + AppendEntriesReply appendEntriesReply = MessageCollectorActor.expectFirstMatching( + leaderActor, AppendEntriesReply.class); + + leader.handleMessage(followerActor, appendEntriesReply); + + // Clear initial heartbeat messages + + leaderActor.underlyingActor().clear(); + followerActor.underlyingActor().clear(); + + // create 3 entries + leaderActorContext.setReplicatedLog( + new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); + leaderActorContext.setCommitIndex(1); + leaderActorContext.setLastApplied(1); + + Uninterruptibles.sleepUninterruptibly(leaderActorContext.getConfigParams().getHeartBeatInterval().toMillis(), + TimeUnit.MILLISECONDS); + + leader.handleMessage(leaderActor, new SendHeartBeat()); + + AppendEntries appendEntries = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); + + // Should send first log entry + assertEquals(1, appendEntries.getLeaderCommit()); + assertEquals(0, appendEntries.getEntries().get(0).getIndex()); + assertEquals(-1, appendEntries.getPrevLogIndex()); + + appendEntriesReply = MessageCollectorActor.expectFirstMatching(leaderActor, AppendEntriesReply.class); + + assertEquals(1, appendEntriesReply.getLogLastTerm()); + assertEquals(0, appendEntriesReply.getLogLastIndex()); + + followerActor.underlyingActor().clear(); + + leader.handleAppendEntriesReply(followerActor, appendEntriesReply); + + appendEntries = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class); + + // Should send second log entry + assertEquals(1, appendEntries.getLeaderCommit()); + assertEquals(1, appendEntries.getEntries().get(0).getIndex()); + + follower.close(); + } + + @Test + public void testLaggingFollowerStarvation() throws Exception { + logStart("testLaggingFollowerStarvation"); + new JavaTestKit(getSystem()) {{ + String leaderActorId = actorFactory.generateActorId("leader"); + String follower1ActorId = actorFactory.generateActorId("follower"); + String follower2ActorId = actorFactory.generateActorId("follower"); + + TestActorRef leaderActor = + actorFactory.createTestActor(ForwardMessageToBehaviorActor.props(), leaderActorId); + ActorRef follower1Actor = actorFactory.createActor(MessageCollectorActor.props(), follower1ActorId); + ActorRef follower2Actor = actorFactory.createActor(MessageCollectorActor.props(), follower2ActorId); + + MockRaftActorContext leaderActorContext = + new MockRaftActorContext(leaderActorId, getSystem(), leaderActor); + + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + configParams.setHeartBeatInterval(new FiniteDuration(200, TimeUnit.MILLISECONDS)); + configParams.setIsolatedLeaderCheckInterval(new FiniteDuration(10, TimeUnit.SECONDS)); + + leaderActorContext.setConfigParams(configParams); + + leaderActorContext.setReplicatedLog( + new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(1,5,1).build()); + + Map peerAddresses = new HashMap<>(); + peerAddresses.put(follower1ActorId, + follower1Actor.path().toString()); + peerAddresses.put(follower2ActorId, + follower2Actor.path().toString()); + + leaderActorContext.setPeerAddresses(peerAddresses); + leaderActorContext.getTermInformation().update(1, leaderActorId); + + RaftActorBehavior leader = createBehavior(leaderActorContext); + + leaderActor.underlyingActor().setBehavior(leader); + + for(int i=1;i<6;i++) { + // Each AppendEntriesReply could end up rescheduling the heartbeat (without the fix for bug 2733) + RaftActorBehavior newBehavior = leader.handleMessage(follower1Actor, new AppendEntriesReply(follower1ActorId, 1, true, i, 1)); + assertTrue(newBehavior == leader); + Uninterruptibles.sleepUninterruptibly(200, TimeUnit.MILLISECONDS); + } + + // Check if the leader has been receiving SendHeartbeat messages despite getting AppendEntriesReply + List heartbeats = MessageCollectorActor.getAllMatching(leaderActor, SendHeartBeat.class); + + assertTrue(String.format("%s heartbeat(s) is less than expected", heartbeats.size()), + heartbeats.size() > 1); + + // Check if follower-2 got AppendEntries during this time and was not starved + List appendEntries = MessageCollectorActor.getAllMatching(follower2Actor, AppendEntries.class); + + assertTrue(String.format("%s append entries is less than expected", appendEntries.size()), + appendEntries.size() > 1); + + }}; + } + + @Override + protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(RaftActorContext actorContext, + ActorRef actorRef, RaftRPC rpc) throws Exception { + super.assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, actorRef, rpc); + assertEquals("New votedFor", null, actorContext.getTermInformation().getVotedFor()); } private class MockConfigParamsImpl extends DefaultConfigParamsImpl { diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/SnapshotTrackerTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/SnapshotTrackerTest.java index 1b3a8f5fb5..f103abcf84 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/SnapshotTrackerTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/SnapshotTrackerTest.java @@ -1,8 +1,6 @@ package org.opendaylight.controller.cluster.raft.behaviors; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import akka.event.LoggingAdapter; import com.google.common.base.Optional; import com.google.protobuf.ByteString; import java.io.ByteArrayOutputStream; @@ -13,9 +11,13 @@ import java.util.Map; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SnapshotTrackerTest { + Logger logger = LoggerFactory.getLogger(getClass()); + Map data; ByteString byteString; ByteString chunk1; @@ -37,14 +39,14 @@ public class SnapshotTrackerTest { @Test public void testAddChunk() throws SnapshotTracker.InvalidChunkException { - SnapshotTracker tracker1 = new SnapshotTracker(mock(LoggingAdapter.class), 5); + SnapshotTracker tracker1 = new SnapshotTracker(logger, 5); tracker1.addChunk(1, chunk1, Optional.absent()); tracker1.addChunk(2, chunk2, Optional.absent()); tracker1.addChunk(3, chunk3, Optional.absent()); // Verify that an InvalidChunkException is thrown when we try to add a chunk to a sealed tracker - SnapshotTracker tracker2 = new SnapshotTracker(mock(LoggingAdapter.class), 2); + SnapshotTracker tracker2 = new SnapshotTracker(logger, 2); tracker2.addChunk(1, chunk1, Optional.absent()); tracker2.addChunk(2, chunk2, Optional.absent()); @@ -57,7 +59,7 @@ public class SnapshotTrackerTest { } // The first chunk's index must at least be FIRST_CHUNK_INDEX - SnapshotTracker tracker3 = new SnapshotTracker(mock(LoggingAdapter.class), 2); + SnapshotTracker tracker3 = new SnapshotTracker(logger, 2); try { tracker3.addChunk(AbstractLeader.FIRST_CHUNK_INDEX - 1, chunk1, Optional.absent()); @@ -67,7 +69,7 @@ public class SnapshotTrackerTest { } // Out of sequence chunk indexes won't work - SnapshotTracker tracker4 = new SnapshotTracker(mock(LoggingAdapter.class), 2); + SnapshotTracker tracker4 = new SnapshotTracker(logger, 2); tracker4.addChunk(AbstractLeader.FIRST_CHUNK_INDEX, chunk1, Optional.absent()); @@ -80,7 +82,7 @@ public class SnapshotTrackerTest { // No exceptions will be thrown when invalid chunk is added with the right sequence // If the lastChunkHashCode is missing - SnapshotTracker tracker5 = new SnapshotTracker(mock(LoggingAdapter.class), 2); + SnapshotTracker tracker5 = new SnapshotTracker(logger, 2); tracker5.addChunk(AbstractLeader.FIRST_CHUNK_INDEX, chunk1, Optional.absent()); // Look I can add the same chunk again @@ -88,7 +90,7 @@ public class SnapshotTrackerTest { // An exception will be thrown when an invalid chunk is addedd with the right sequence // when the lastChunkHashCode is present - SnapshotTracker tracker6 = new SnapshotTracker(mock(LoggingAdapter.class), 2); + SnapshotTracker tracker6 = new SnapshotTracker(logger, 2); tracker6.addChunk(AbstractLeader.FIRST_CHUNK_INDEX, chunk1, Optional.of(-1)); @@ -106,7 +108,7 @@ public class SnapshotTrackerTest { public void testGetSnapShot() throws SnapshotTracker.InvalidChunkException { // Trying to get a snapshot before all chunks have been received will throw an exception - SnapshotTracker tracker1 = new SnapshotTracker(mock(LoggingAdapter.class), 5); + SnapshotTracker tracker1 = new SnapshotTracker(logger, 5); tracker1.addChunk(1, chunk1, Optional.absent()); try { @@ -116,7 +118,7 @@ public class SnapshotTrackerTest { } - SnapshotTracker tracker2 = new SnapshotTracker(mock(LoggingAdapter.class), 3); + SnapshotTracker tracker2 = new SnapshotTracker(logger, 3); tracker2.addChunk(1, chunk1, Optional.absent()); tracker2.addChunk(2, chunk2, Optional.absent()); @@ -129,7 +131,7 @@ public class SnapshotTrackerTest { @Test public void testGetCollectedChunks() throws SnapshotTracker.InvalidChunkException { - SnapshotTracker tracker1 = new SnapshotTracker(mock(LoggingAdapter.class), 5); + SnapshotTracker tracker1 = new SnapshotTracker(logger, 5); ByteString chunks = chunk1.concat(chunk2); diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/messages/AppendEntriesTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/messages/AppendEntriesTest.java index abde51bde5..5f5d73dbe6 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/messages/AppendEntriesTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/messages/AppendEntriesTest.java @@ -7,8 +7,6 @@ */ package org.opendaylight.controller.cluster.raft.messages; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -21,6 +19,9 @@ import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; import org.opendaylight.controller.cluster.raft.ReplicatedLogImplEntry; import org.opendaylight.controller.protobuff.messages.cluster.raft.AppendEntriesMessages; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + /** * Unit tests for AppendEntries. * @@ -34,7 +35,7 @@ public class AppendEntriesTest { ReplicatedLogEntry entry2 = new ReplicatedLogImplEntry(3, 4, new MockPayload("payload2")); - AppendEntries expected = new AppendEntries(5L, "node1", 7L, 8L, Arrays.asList(entry1, entry2), 10L); + AppendEntries expected = new AppendEntries(5L, "node1", 7L, 8L, Arrays.asList(entry1, entry2), 10L, -1); AppendEntries cloned = (AppendEntries) SerializationUtils.clone(expected); @@ -44,7 +45,7 @@ public class AppendEntriesTest { @Test public void testToAndFromSerializable() { AppendEntries entries = new AppendEntries(5L, "node1", 7L, 8L, - Collections.emptyList(), 10L); + Collections.emptyList(), 10L, -1); assertSame("toSerializable", entries, entries.toSerializable()); assertSame("fromSerializable", entries, @@ -54,7 +55,7 @@ public class AppendEntriesTest { @Test public void testToAndFromLegacySerializable() { ReplicatedLogEntry entry = new ReplicatedLogImplEntry(3, 4, new MockPayload("payload")); - AppendEntries entries = new AppendEntries(5L, "node1", 7L, 8L, Arrays.asList(entry), 10L); + AppendEntries entries = new AppendEntries(5L, "node1", 7L, 8L, Arrays.asList(entry), 10L, -1); Object serializable = entries.toSerializable(RaftVersions.HELIUM_VERSION); Assert.assertTrue(serializable instanceof AppendEntriesMessages.AppendEntries); @@ -71,6 +72,7 @@ public class AppendEntriesTest { assertEquals("getLeaderCommit", expected.getLeaderCommit(), actual.getLeaderCommit()); assertEquals("getPrevLogIndex", expected.getPrevLogIndex(), actual.getPrevLogIndex()); assertEquals("getPrevLogTerm", expected.getPrevLogTerm(), actual.getPrevLogTerm()); + assertEquals("getReplicatedToAllIndex", expected.getReplicatedToAllIndex(), actual.getReplicatedToAllIndex()); assertEquals("getEntries size", expected.getEntries().size(), actual.getEntries().size()); Iterator iter = expected.getEntries().iterator(); diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/ForwardMessageToBehaviorActor.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/ForwardMessageToBehaviorActor.java new file mode 100644 index 0000000000..9bcfcd9091 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/ForwardMessageToBehaviorActor.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.cluster.raft.utils; + +import akka.actor.Props; +import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior; + +public class ForwardMessageToBehaviorActor extends MessageCollectorActor { + private RaftActorBehavior behavior; + + @Override + public void onReceive(Object message) throws Exception { + if(behavior != null) { + behavior.handleMessage(sender(), message); + } + + super.onReceive(message); + } + + public static Props props() { + return Props.create(ForwardMessageToBehaviorActor.class); + } + + public void setBehavior(RaftActorBehavior behavior){ + this.behavior = behavior; + } +} + diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/MessageCollectorActor.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/MessageCollectorActor.java index 3469a956c3..62f163fb7d 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/MessageCollectorActor.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/MessageCollectorActor.java @@ -9,13 +9,17 @@ package org.opendaylight.controller.cluster.raft.utils; import akka.actor.ActorRef; +import akka.actor.Props; import akka.actor.UntypedActor; import akka.pattern.Patterns; import akka.util.Timeout; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Uninterruptibles; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.Assert; import scala.concurrent.Await; import scala.concurrent.Future; import scala.concurrent.duration.Duration; @@ -23,28 +27,35 @@ import scala.concurrent.duration.FiniteDuration; public class MessageCollectorActor extends UntypedActor { - private List messages = new ArrayList<>(); + private static final String ARE_YOU_READY = "ARE_YOU_READY"; + + private final List messages = new ArrayList<>(); @Override public void onReceive(Object message) throws Exception { + if(message.equals(ARE_YOU_READY)) { + getSender().tell("yes", getSelf()); + return; + } + if(message instanceof String){ if("get-all-messages".equals(message)){ - getSender().tell(new ArrayList(messages), getSelf()); + getSender().tell(new ArrayList<>(messages), getSelf()); } - } else { + } else if(message != null) { messages.add(message); } } + public void clear() { + messages.clear(); + } + public static List getAllMessages(ActorRef actor) throws Exception { FiniteDuration operationDuration = Duration.create(5, TimeUnit.SECONDS); Timeout operationTimeout = new Timeout(operationDuration); Future future = Patterns.ask(actor, "get-all-messages", operationTimeout); - try { - return (List) Await.result(future, operationDuration); - } catch (Exception e) { - throw e; - } + return (List) Await.result(future, operationDuration); } /** @@ -53,30 +64,68 @@ public class MessageCollectorActor extends UntypedActor { * @param clazz * @return */ - public static Object getFirstMatching(ActorRef actor, Class clazz) throws Exception { + public static T getFirstMatching(ActorRef actor, Class clazz) throws Exception { List allMessages = getAllMessages(actor); for(Object message : allMessages){ if(message.getClass().equals(clazz)){ - return message; + return (T) message; } } return null; } - public static List getAllMatching(ActorRef actor, Class clazz) throws Exception { + public static T expectFirstMatching(ActorRef actor, Class clazz) { + return expectFirstMatching(actor, clazz, 5000); + } + + public static T expectFirstMatching(ActorRef actor, Class clazz, long timeout) { + int count = (int) (timeout / 50); + for(int i = 0; i < count; i++) { + try { + T message = getFirstMatching(actor, clazz); + if(message != null) { + return message; + } + } catch (Exception e) {} + + Uninterruptibles.sleepUninterruptibly(50, TimeUnit.MILLISECONDS); + } + + Assert.fail("Did not receive message of type " + clazz); + return null; + } + + public static List getAllMatching(ActorRef actor, Class clazz) throws Exception { List allMessages = getAllMessages(actor); - List output = Lists.newArrayList(); + List output = Lists.newArrayList(); for(Object message : allMessages){ if(message.getClass().equals(clazz)){ - output.add(message); + output.add((T) message); } } return output; } + public static void waitUntilReady(ActorRef actor) throws Exception { + long timeout = 500; + FiniteDuration duration = Duration.create(timeout, TimeUnit.MILLISECONDS); + for(int i = 0; i < 10; i++) { + try { + Await.ready(Patterns.ask(actor, ARE_YOU_READY, timeout), duration); + return; + } catch (TimeoutException e) { + } + } + + throw new TimeoutException("Actor not ready in time."); + } + + public static Props props() { + return Props.create(MessageCollectorActor.class); + } } diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/resources/simplelogger.properties b/opendaylight/md-sal/sal-akka-raft/src/test/resources/simplelogger.properties new file mode 100644 index 0000000000..853fc755d2 --- /dev/null +++ b/opendaylight/md-sal/sal-akka-raft/src/test/resources/simplelogger.properties @@ -0,0 +1,6 @@ +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=hh:mm:ss,S a +org.slf4j.simpleLogger.logFile=System.out +org.slf4j.simpleLogger.showShortLogName=true +org.slf4j.simpleLogger.levelInBrackets=true +org.slf4j.simpleLogger.log.org.opendaylight.controller.cluster.raft=trace \ No newline at end of file diff --git a/opendaylight/md-sal/sal-binding-dom-it/pom.xml b/opendaylight/md-sal/sal-binding-dom-it/pom.xml index 01cd1f88ba..d6a5e498b6 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/pom.xml +++ b/opendaylight/md-sal/sal-binding-dom-it/pom.xml @@ -29,11 +29,6 @@ test-jar test - - org.opendaylight.controller.model - model-flow-service - test - org.ops4j.pax.exam pax-exam-container-native @@ -49,10 +44,14 @@ yang-parser-impl test - - org.opendaylight.controller - sal-test-model - + + org.opendaylight.controller + sal-test-model + + + org.opendaylight.yangtools.model + opendaylight-l2-types + diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java index aefc53b124..3d25018e24 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/ConcurrentImplicitCreateTest.java @@ -17,26 +17,25 @@ import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; -/* +/** * FIXME: THis test should be moved to sal-binding-broker and rewriten * to use new DataBroker API */ @SuppressWarnings("deprecation") public class ConcurrentImplicitCreateTest extends AbstractDataServiceTest { - private static final NodeKey NODE_FOO_KEY = new NodeKey(new NodeId("foo")); - private static final NodeKey NODE_BAR_KEY = new NodeKey(new NodeId("foo")); - private static InstanceIdentifier NODES_PATH = InstanceIdentifier.builder(Nodes.class).build(); - private static InstanceIdentifier NODE_FOO_PATH = NODES_PATH.child(Node.class, NODE_FOO_KEY); - private static InstanceIdentifier NODE_BAR_PATH = NODES_PATH.child(Node.class, NODE_FOO_KEY); + private static final TopLevelListKey FOO_KEY = new TopLevelListKey("foo"); + private static final TopLevelListKey BAR_KEY = new TopLevelListKey("bar"); + private static InstanceIdentifier TOP_PATH = InstanceIdentifier.builder(Top.class).build(); + private static InstanceIdentifier FOO_PATH = TOP_PATH.child(TopLevelList.class, FOO_KEY); + private static InstanceIdentifier BAR_PATH = TOP_PATH.child(TopLevelList.class, BAR_KEY); @Test public void testConcurrentCreate() throws InterruptedException, ExecutionException { @@ -44,8 +43,8 @@ public class ConcurrentImplicitCreateTest extends AbstractDataServiceTest { DataModificationTransaction fooTx = baDataService.beginTransaction(); DataModificationTransaction barTx = baDataService.beginTransaction(); - fooTx.putOperationalData(NODE_FOO_PATH, new NodeBuilder().setKey(NODE_FOO_KEY).build()); - barTx.putOperationalData(NODE_BAR_PATH, new NodeBuilder().setKey(NODE_BAR_KEY).build()); + fooTx.putOperationalData(FOO_PATH, new TopLevelListBuilder().setKey(FOO_KEY).build()); + barTx.putOperationalData(BAR_PATH, new TopLevelListBuilder().setKey(BAR_KEY).build()); Future> fooFuture = fooTx.commit(); Future> barFuture = barTx.commit(); diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java index 2b5171369b..0a611a75d0 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java @@ -22,87 +22,75 @@ import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.api.data.DataProviderService; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeatures; -import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeaturesBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeaturesKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.TreeComplexUsesAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.TreeComplexUsesAugmentBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.complex.from.grouping.ContainerWithUses; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.complex.from.grouping.ContainerWithUsesBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.complex.from.grouping.ListViaUses; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.complex.from.grouping.ListViaUsesBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.complex.from.grouping.ListViaUsesKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import com.google.common.util.concurrent.SettableFuture; -/* - * FIXME: THis test should be moved to compat test-suite and rewriten - * to use sal-test-model +/** + * FIXME: THis test should be moved to compat test-suite */ @SuppressWarnings("deprecation") public class WildcardedDataChangeListenerTest extends AbstractDataServiceTest { - private static final NodeKey NODE_0_KEY = new NodeKey(new NodeId("test:0")); - private static final NodeKey NODE_1_KEY = new NodeKey(new NodeId("test:1")); + private static final TopLevelListKey TOP_LEVEL_LIST_0_KEY = new TopLevelListKey("test:0"); + private static final TopLevelListKey TOP_LEVEL_LIST_1_KEY = new TopLevelListKey("test:1"); - public static final InstanceIdentifier DEEP_WILDCARDED_PATH = InstanceIdentifier.builder(Nodes.class) - .child(Node.class) // - .augmentation(FlowCapableNode.class) // - .child(Table.class) // - .child(Flow.class) // + protected static final InstanceIdentifier DEEP_WILDCARDED_PATH = InstanceIdentifier + .builder(Top.class) + .child(TopLevelList.class) // + .augmentation(TreeComplexUsesAugment.class) // + .child(ListViaUses.class) // .build(); - private static final TableKey TABLE_0_KEY = new TableKey((short) 0); - private static final TableFeaturesKey TABLE_FEATURES_KEY = new TableFeaturesKey((short) 0); - - private static final InstanceIdentifier NODE_0_TABLE_PATH = InstanceIdentifier.builder(Nodes.class) - .child(Node.class, NODE_0_KEY) // - .augmentation(FlowCapableNode.class) // - .child(Table.class, TABLE_0_KEY) // + private static final InstanceIdentifier NODE_0_TCU_PATH = InstanceIdentifier + .builder(Top.class) + .child(TopLevelList.class, TOP_LEVEL_LIST_0_KEY) // + .augmentation(TreeComplexUsesAugment.class) // .build(); - private static final InstanceIdentifier
NODE_1_TABLE_PATH = InstanceIdentifier.builder(Nodes.class) - .child(Node.class, NODE_1_KEY) // - .augmentation(FlowCapableNode.class) // - .child(Table.class, TABLE_0_KEY) // + private static final InstanceIdentifier NODE_1_TCU_PATH = InstanceIdentifier + .builder(Top.class) + .child(TopLevelList.class, TOP_LEVEL_LIST_1_KEY) // + .augmentation(TreeComplexUsesAugment.class) // .build(); - private static final FlowKey FLOW_KEY = new FlowKey(new FlowId("test")); - private static final InstanceIdentifier NODE_0_FLOW_PATH = NODE_0_TABLE_PATH.child(Flow.class, FLOW_KEY); + private static final ListViaUsesKey LIST_VIA_USES_KEY = new ListViaUsesKey("test"); + + private static final InstanceIdentifier NODE_0_LVU_PATH = NODE_0_TCU_PATH.child(ListViaUses.class, LIST_VIA_USES_KEY); - private static final InstanceIdentifier NODE_1_FLOW_PATH = NODE_1_TABLE_PATH.child(Flow.class, FLOW_KEY); + private static final InstanceIdentifier NODE_1_LVU_PATH = NODE_1_TCU_PATH.child(ListViaUses.class, LIST_VIA_USES_KEY); - private static final InstanceIdentifier NODE_0_TABLE_FEATURES_PATH = - NODE_0_TABLE_PATH.child(TableFeatures.class, TABLE_FEATURES_KEY); + private static final InstanceIdentifier NODE_0_CWU_PATH = + NODE_0_TCU_PATH.child(ContainerWithUses.class); - private static final TableFeatures TABLE_FEATURES = new TableFeaturesBuilder()// - .setKey(TABLE_FEATURES_KEY) // - .setName("Foo") // - .setMaxEntries(1000L) // + private static final ContainerWithUses CWU= new ContainerWithUsesBuilder()// + .setLeafFromGrouping("some container value") // .build(); - private static final Flow FLOW = new FlowBuilder() // - .setKey(FLOW_KEY) // - .setBarrier(true) // - .setStrict(true) // + private static final ListViaUses LVU = new ListViaUsesBuilder() // + .setKey(LIST_VIA_USES_KEY) // + .setName("john") .build(); @Test - public void testSepareteWrites() throws InterruptedException, TimeoutException, ExecutionException { + public void testSeparateWrites() throws InterruptedException, TimeoutException, ExecutionException { DataProviderService dataBroker = testContext.getBindingDataBroker(); final SettableFuture, DataObject>> eventFuture = SettableFuture.create(); dataBroker.registerDataChangeListener(DEEP_WILDCARDED_PATH, new DataChangeListener() { - @Override public void onDataChanged(final DataChangeEvent, DataObject> dataChangeEvent) { eventFuture.set(dataChangeEvent); @@ -110,9 +98,9 @@ public class WildcardedDataChangeListenerTest extends AbstractDataServiceTest { }); DataModificationTransaction transaction = dataBroker.beginTransaction(); - transaction.putOperationalData(NODE_0_TABLE_FEATURES_PATH, TABLE_FEATURES); - transaction.putOperationalData(NODE_0_FLOW_PATH, FLOW); - transaction.putOperationalData(NODE_1_FLOW_PATH, FLOW); + transaction.putOperationalData(NODE_0_CWU_PATH, CWU); + transaction.putOperationalData(NODE_0_LVU_PATH, LVU); + transaction.putOperationalData(NODE_1_LVU_PATH, LVU); transaction.commit().get(); DataChangeEvent, DataObject> event = eventFuture.get(1000, TimeUnit.MILLISECONDS); @@ -127,29 +115,26 @@ public class WildcardedDataChangeListenerTest extends AbstractDataServiceTest { final SettableFuture, DataObject>> eventFuture = SettableFuture.create(); dataBroker.registerDataChangeListener(DEEP_WILDCARDED_PATH, new DataChangeListener() { - @Override public void onDataChanged(final DataChangeEvent, DataObject> dataChangeEvent) { eventFuture.set(dataChangeEvent); } }); - DataModificationTransaction tableTx = dataBroker.beginTransaction(); - tableTx.putOperationalData(NODE_0_TABLE_FEATURES_PATH, TABLE_FEATURES); - tableTx.commit().get(); + DataModificationTransaction cwuTx = dataBroker.beginTransaction(); + cwuTx.putOperationalData(NODE_0_CWU_PATH, CWU); + cwuTx.commit().get(); assertFalse(eventFuture.isDone()); - DataModificationTransaction flowTx = dataBroker.beginTransaction(); + DataModificationTransaction lvuTx = dataBroker.beginTransaction(); - Table table = new TableBuilder() // - .setKey(TABLE_0_KEY) // - .setFlow(Collections.singletonList(FLOW)) // - .build(); + TreeComplexUsesAugment tcua = new TreeComplexUsesAugmentBuilder() + .setListViaUses(Collections.singletonList(LVU)).build(); - flowTx.putOperationalData(NODE_0_TABLE_PATH, table); - flowTx.putOperationalData(NODE_1_FLOW_PATH, FLOW); - flowTx.commit().get(); + lvuTx.putOperationalData(NODE_0_TCU_PATH, tcua); + lvuTx.putOperationalData(NODE_1_LVU_PATH, LVU); + lvuTx.commit().get(); validateEvent(eventFuture.get(1000, TimeUnit.MILLISECONDS)); } @@ -161,7 +146,7 @@ public class WildcardedDataChangeListenerTest extends AbstractDataServiceTest { // We wrote initial state NODE_0_FLOW DataModificationTransaction transaction = dataBroker.beginTransaction(); - transaction.putOperationalData(NODE_0_FLOW_PATH, FLOW); + transaction.putOperationalData(NODE_0_LVU_PATH, LVU); transaction.commit().get(); // We registered DataChangeListener @@ -176,23 +161,23 @@ public class WildcardedDataChangeListenerTest extends AbstractDataServiceTest { assertFalse(eventFuture.isDone()); DataModificationTransaction secondTx = dataBroker.beginTransaction(); - secondTx.putOperationalData(NODE_0_FLOW_PATH, FLOW); - secondTx.putOperationalData(NODE_1_FLOW_PATH, FLOW); + secondTx.putOperationalData(NODE_0_LVU_PATH, LVU); + secondTx.putOperationalData(NODE_1_LVU_PATH, LVU); secondTx.commit().get(); DataChangeEvent, DataObject> event = (eventFuture.get(1000, TimeUnit.MILLISECONDS)); assertNotNull(event); // Data change should contains NODE_1 Flow - which was added - assertTrue(event.getCreatedOperationalData().containsKey(NODE_1_FLOW_PATH)); + assertTrue(event.getCreatedOperationalData().containsKey(NODE_1_LVU_PATH)); // Data change must not containe NODE_0 Flow which was replaced with same value. - assertFalse(event.getUpdatedOperationalData().containsKey(NODE_0_FLOW_PATH)); + assertFalse(event.getUpdatedOperationalData().containsKey(NODE_0_LVU_PATH)); } private static void validateEvent(final DataChangeEvent, DataObject> event) { assertNotNull(event); - assertTrue(event.getCreatedOperationalData().containsKey(NODE_1_FLOW_PATH)); - assertTrue(event.getCreatedOperationalData().containsKey(NODE_0_FLOW_PATH)); - assertFalse(event.getCreatedOperationalData().containsKey(NODE_0_TABLE_FEATURES_PATH)); + assertTrue(event.getCreatedOperationalData().containsKey(NODE_1_LVU_PATH)); + assertTrue(event.getCreatedOperationalData().containsKey(NODE_0_LVU_PATH)); + assertFalse(event.getCreatedOperationalData().containsKey(NODE_0_CWU_PATH)); } } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DOMCodecBug02Test.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DOMCodecBug02Test.java index bddbc4e954..d85cb7a0a2 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DOMCodecBug02Test.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DOMCodecBug02Test.java @@ -21,9 +21,9 @@ import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; import org.opendaylight.controller.sal.binding.test.util.BindingBrokerTestFactory; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodesBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.TopBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; @@ -33,7 +33,7 @@ import com.google.common.util.concurrent.MoreExecutors; @SuppressWarnings("deprecation") public class DOMCodecBug02Test extends AbstractDataServiceTest { - private static final InstanceIdentifier NODES_INSTANCE_ID_BA = InstanceIdentifier.builder(Nodes.class) // + private static final InstanceIdentifier TOP_INSTANCE_ID_BA = InstanceIdentifier.builder(Top.class) // .toInstance(); /** @@ -66,10 +66,10 @@ public class DOMCodecBug02Test extends AbstractDataServiceTest { .submit(new Callable>>() { @Override public Future> call() throws Exception { - NodesBuilder nodesBuilder = new NodesBuilder(); - nodesBuilder.setNode(Collections. emptyList()); + TopBuilder topBuilder = new TopBuilder(); + topBuilder.setTopLevelList(Collections. emptyList()); DataModificationTransaction transaction = baDataService.beginTransaction(); - transaction.putOperationalData(NODES_INSTANCE_ID_BA, nodesBuilder.build()); + transaction.putOperationalData(TOP_INSTANCE_ID_BA, topBuilder.build()); return transaction.commit(); } }); @@ -77,13 +77,13 @@ public class DOMCodecBug02Test extends AbstractDataServiceTest { RpcResult result = future.get().get(); assertEquals(TransactionStatus.COMMITED, result.getResult()); - Nodes nodes = checkForNodes(); - assertNotNull(nodes); + Top top = checkForTop(); + assertNotNull(top); } - private Nodes checkForNodes() { - return (Nodes) baDataService.readOperationalData(NODES_INSTANCE_ID_BA); + private Top checkForTop() { + return (Top) baDataService.readOperationalData(TOP_INSTANCE_ID_BA); } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DOMCodecBug03Test.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DOMCodecBug03Test.java index c07125a5dc..ba4a024b7c 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DOMCodecBug03Test.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DOMCodecBug03Test.java @@ -22,22 +22,20 @@ import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.flow.node.SupportedActions; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.flow.node.SupportedActionsBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.flow.node.supported.actions.ActionType; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.flow.node.supported.actions.ActionTypeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.SupportType; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.CustomEnum; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.TllComplexAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.TllComplexAugmentBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.Cont2; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.Cont2Builder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.cont2.Contlist1; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.cont2.Contlist1Builder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.top.level.list.NestedList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.top.level.list.NestedListBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.top.level.list.NestedListKey; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; @@ -49,42 +47,42 @@ import com.google.common.util.concurrent.SettableFuture; @SuppressWarnings("deprecation") public class DOMCodecBug03Test extends AbstractDataServiceTest implements DataChangeListener { - private static final QName NODE_ID_QNAME = QName.create(Node.QNAME, "id"); - private static final String NODE_ID = "openflow:1"; + private static final QName TOP_LEVEL_LIST_NAME_QNAME = QName.create(TopLevelList.QNAME, "name"); + private static final String TOP_LEVEL_LIST_NAME = "tll:foo"; - private static final NodeKey NODE_KEY = new NodeKey(new NodeId(NODE_ID)); + private static final TopLevelListKey TLL_KEY = new TopLevelListKey(TOP_LEVEL_LIST_NAME); - private static final Map NODE_KEY_BI = Collections. singletonMap(NODE_ID_QNAME, - NODE_ID); + private static final Map TLL_KEY_BI = Collections. singletonMap(TOP_LEVEL_LIST_NAME_QNAME, + TOP_LEVEL_LIST_NAME); - private static final InstanceIdentifier NODES_INSTANCE_ID_BA = InstanceIdentifier.builder(Nodes.class) // + private static final InstanceIdentifier TOP_INSTANCE_ID_BA = InstanceIdentifier.builder(Top.class) // .toInstance(); - private static final InstanceIdentifier NODE_INSTANCE_ID_BA = NODES_INSTANCE_ID_BA.child(Node.class, NODE_KEY); + private static final InstanceIdentifier TLL_INSTANCE_ID_BA = TOP_INSTANCE_ID_BA.child(TopLevelList.class, TLL_KEY); - private static final InstanceIdentifier SUPPORTED_ACTIONS_INSTANCE_ID_BA = // - NODES_INSTANCE_ID_BA.builder() // - .child(Node.class, NODE_KEY) // - .augmentation(FlowCapableNode.class) // - .child(SupportedActions.class) + private static final InstanceIdentifier CONT2_INSTANCE_ID_BA = // + TOP_INSTANCE_ID_BA.builder() // + .child(TopLevelList.class, TLL_KEY) // + .augmentation(TllComplexAugment.class) // + .child(Cont2.class) .toInstance(); - private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier NODE_INSTANCE_ID_BI = // + private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier TLL_INSTANCE_ID_BI = // org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.builder() // - .node(Nodes.QNAME) // - .nodeWithKey(Node.QNAME, NODE_KEY_BI) // + .node(Top.QNAME) // + .nodeWithKey(TopLevelList.QNAME, TLL_KEY_BI) // .toInstance(); - private static final QName SUPPORTED_ACTIONS_QNAME = QName.create(FlowCapableNode.QNAME, SupportedActions.QNAME.getLocalName()); + private static final QName CONT2_QNAME = QName.create(TllComplexAugment.QNAME, Cont2.QNAME.getLocalName()); - private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier SUPPORTED_ACTIONS_INSTANCE_ID_BI = // + private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier CONT2_INSTANCE_ID_BI = // org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.builder() // - .node(Nodes.QNAME) // - .nodeWithKey(Node.QNAME, NODE_KEY_BI) // - .node(SUPPORTED_ACTIONS_QNAME) // + .node(Top.QNAME) // + .nodeWithKey(TopLevelList.QNAME, TLL_KEY_BI) // + .node(CONT2_QNAME) // .toInstance(); private final SettableFuture, DataObject>> receivedChangeEvent = SettableFuture.create(); @@ -100,24 +98,22 @@ public class DOMCodecBug03Test extends AbstractDataServiceTest implements DataCh public void testAugmentSerialization() throws Exception { - baDataService.registerDataChangeListener(NODES_INSTANCE_ID_BA, this); + baDataService.registerDataChangeListener(TOP_INSTANCE_ID_BA, this); - NodeBuilder nodeBuilder = new NodeBuilder(); - nodeBuilder.setId(new NodeId(NODE_ID)); - nodeBuilder.setKey(NODE_KEY); + TopLevelListBuilder tllBuilder = new TopLevelListBuilder(); + tllBuilder.setKey(TLL_KEY); DataModificationTransaction transaction = baDataService.beginTransaction(); - FlowCapableNodeBuilder fnub = new FlowCapableNodeBuilder(); - fnub.setHardware("Hardware Foo"); - fnub.setManufacturer("Manufacturer Foo"); - fnub.setSerialNumber("Serial Foo"); - fnub.setDescription("Description Foo"); - fnub.setSoftware("JUnit emulated"); - FlowCapableNode fnu = fnub.build(); - nodeBuilder.addAugmentation(FlowCapableNode.class, fnu); - Node original = nodeBuilder.build(); - transaction.putOperationalData(NODE_INSTANCE_ID_BA, original); + TllComplexAugmentBuilder tllcab = new TllComplexAugmentBuilder(); + tllcab.setAttrStr1("Hardware Foo"); + tllcab.setAttrStr2("Manufacturer Foo"); + tllcab.setAttrStr3("Serial Foo"); + tllcab.setAttrStr4("Description Foo"); + TllComplexAugment tlca = tllcab.build(); + tllBuilder.addAugmentation(TllComplexAugment.class, tlca); + TopLevelList original = tllBuilder.build(); + transaction.putOperationalData(TLL_INSTANCE_ID_BA, original); RpcResult result = transaction.commit().get(); assertEquals(TransactionStatus.COMMITED, result.getResult()); @@ -125,13 +121,13 @@ public class DOMCodecBug03Test extends AbstractDataServiceTest implements DataCh DataChangeEvent, DataObject> potential = receivedChangeEvent.get(1000,TimeUnit.MILLISECONDS); assertNotNull(potential); - verifyNodes((Nodes) potential.getUpdatedOperationalSubtree(),original); - assertBindingIndependentVersion(NODE_INSTANCE_ID_BI); - Nodes nodes = checkForNodes(); - verifyNodes(nodes,original); + verifyTll((Top) potential.getUpdatedOperationalSubtree(),original); + assertBindingIndependentVersion(TLL_INSTANCE_ID_BI); + Top top = checkForTop(); + verifyTll(top,original); testAddingNodeConnector(); - testNodeRemove(); + testTllRemove(); } @@ -139,69 +135,66 @@ public class DOMCodecBug03Test extends AbstractDataServiceTest implements DataCh public void testAugmentNestedSerialization() throws Exception { DataModificationTransaction transaction = baDataService.beginTransaction(); - SupportedActionsBuilder actions = new SupportedActionsBuilder(); - ActionTypeBuilder action = new ActionTypeBuilder(); - action.setAction("foo-action"); - action.setSupportState(SupportType.Native); - List actionTypes = Collections.singletonList(action.build()); - actions.setActionType(actionTypes ); + Cont2Builder cont2b = new Cont2Builder(); + Contlist1Builder cl1b = new Contlist1Builder(); + cl1b.setAttrStr("foo-action"); + cl1b.setAttrEnum(CustomEnum.Type1); + List contlists = Collections.singletonList(cl1b.build()); + cont2b.setContlist1(contlists); - transaction.putOperationalData(SUPPORTED_ACTIONS_INSTANCE_ID_BA, actions.build()); + transaction.putOperationalData(CONT2_INSTANCE_ID_BA, cont2b.build()); RpcResult putResult = transaction.commit().get(); assertNotNull(putResult); assertEquals(TransactionStatus.COMMITED, putResult.getResult()); - SupportedActions readedTable = (SupportedActions) baDataService.readOperationalData(SUPPORTED_ACTIONS_INSTANCE_ID_BA); + Cont2 readedTable = (Cont2) baDataService.readOperationalData(CONT2_INSTANCE_ID_BA); assertNotNull(readedTable); - CompositeNode biSupportedActions = biDataService.readOperationalData(SUPPORTED_ACTIONS_INSTANCE_ID_BI); + CompositeNode biSupportedActions = biDataService.readOperationalData(CONT2_INSTANCE_ID_BI); assertNotNull(biSupportedActions); } private void testAddingNodeConnector() throws Exception { - - NodeConnectorId ncId = new NodeConnectorId("openflow:1:bar"); - NodeConnectorKey nodeKey = new NodeConnectorKey(ncId ); - InstanceIdentifier ncInstanceId = NODE_INSTANCE_ID_BA.child(NodeConnector.class, nodeKey); - NodeConnectorBuilder ncBuilder = new NodeConnectorBuilder(); - ncBuilder.setId(ncId); - ncBuilder.setKey(nodeKey); - NodeConnector connector = ncBuilder.build(); + NestedListKey nlKey = new NestedListKey("test:0:0"); + InstanceIdentifier ncInstanceId = TLL_INSTANCE_ID_BA.child(NestedList.class, nlKey); + NestedListBuilder nlBuilder = new NestedListBuilder(); + nlBuilder.setKey(nlKey); + NestedList nestedList = nlBuilder.build(); DataModificationTransaction transaction = baDataService.beginTransaction(); - transaction.putOperationalData(ncInstanceId, connector); + transaction.putOperationalData(ncInstanceId, nestedList); RpcResult result = transaction.commit().get(); assertEquals(TransactionStatus.COMMITED, result.getResult()); - Node node = (Node) baDataService.readOperationalData(NODE_INSTANCE_ID_BA); - assertNotNull(node); - assertNotNull(node.getNodeConnector()); - assertFalse(node.getNodeConnector().isEmpty()); - NodeConnector readedNc = node.getNodeConnector().get(0); - assertNotNull(readedNc); + TopLevelList tll = (TopLevelList) baDataService.readOperationalData(TLL_INSTANCE_ID_BA); + assertNotNull(tll); + assertNotNull(tll.getNestedList()); + assertFalse(tll.getNestedList().isEmpty()); + NestedList readedNl = tll.getNestedList().get(0); + assertNotNull(readedNl); } - private void testNodeRemove() throws Exception { + private void testTllRemove() throws Exception { DataModificationTransaction transaction = baDataService.beginTransaction(); - transaction.removeOperationalData(NODE_INSTANCE_ID_BA); + transaction.removeOperationalData(TLL_INSTANCE_ID_BA); RpcResult result = transaction.commit().get(); assertEquals(TransactionStatus.COMMITED, result.getResult()); - Node node = (Node) baDataService.readOperationalData(NODE_INSTANCE_ID_BA); - assertNull(node); + TopLevelList tll = (TopLevelList) baDataService.readOperationalData(TLL_INSTANCE_ID_BA); + assertNull(tll); } - private void verifyNodes(final Nodes nodes,final Node original) { - assertNotNull(nodes); - assertNotNull(nodes.getNode()); - assertEquals(1, nodes.getNode().size()); - Node readedNode = nodes.getNode().get(0); - assertEquals(original.getId(), readedNode.getId()); + private void verifyTll(final Top top,final TopLevelList original) { + assertNotNull(top); + assertNotNull(top.getTopLevelList()); + assertEquals(1, top.getTopLevelList().size()); + TopLevelList readedNode = top.getTopLevelList().get(0); + assertEquals(original.getName(), readedNode.getName()); assertEquals(original.getKey(), readedNode.getKey()); - FlowCapableNode fnu = original.getAugmentation(FlowCapableNode.class); - FlowCapableNode readedAugment = readedNode.getAugmentation(FlowCapableNode.class); + TllComplexAugment fnu = original.getAugmentation(TllComplexAugment.class); + TllComplexAugment readedAugment = readedNode.getAugmentation(TllComplexAugment.class); assertNotNull(fnu); - assertEquals(fnu.getDescription(), readedAugment.getDescription()); - assertEquals(fnu.getSerialNumber(), readedAugment.getSerialNumber()); + assertEquals(fnu.getAttrStr2(), readedAugment.getAttrStr2()); + assertEquals(fnu.getAttrStr3(), readedAugment.getAttrStr3()); } @@ -211,8 +204,8 @@ public class DOMCodecBug03Test extends AbstractDataServiceTest implements DataCh assertNotNull(node); } - private Nodes checkForNodes() { - return (Nodes) baDataService.readOperationalData(NODES_INSTANCE_ID_BA); + private Top checkForTop() { + return (Top) baDataService.readOperationalData(TOP_INSTANCE_ID_BA); } @Override diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DeleteNestedAugmentationListenParentTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DeleteNestedAugmentationListenParentTest.java index 735138a530..40d4591001 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DeleteNestedAugmentationListenParentTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/DeleteNestedAugmentationListenParentTest.java @@ -9,21 +9,17 @@ import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowStatisticsData; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowStatisticsDataBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.flow.statistics.FlowStatisticsBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.List11SimpleAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.List11SimpleAugmentBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.TllComplexAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11Builder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; @@ -32,20 +28,20 @@ import com.google.common.util.concurrent.SettableFuture; @SuppressWarnings("deprecation") public class DeleteNestedAugmentationListenParentTest extends AbstractDataServiceTest { - private static final NodeKey NODE_KEY = new NodeKey(new NodeId("foo")); + private static final TopLevelListKey FOO_KEY = new TopLevelListKey("foo"); - private static final TableKey TABLE_KEY = new TableKey((short) 0); + private static final List1Key LIST1_KEY = new List1Key("one"); - private static final FlowKey FLOW_KEY = new FlowKey(new FlowId("100")); + private static final List11Key LIST11_KEY = new List11Key(100); - private static final InstanceIdentifier NODE_AUGMENT_PATH = InstanceIdentifier.builder(Nodes.class) - .child(Node.class,NODE_KEY) - .augmentation(FlowCapableNode.class) + private static final InstanceIdentifier TLL_COMPLEX_AUGMENT_PATH = InstanceIdentifier.builder(Top.class) + .child(TopLevelList.class,FOO_KEY) + .augmentation(TllComplexAugment.class) .build(); - private static final InstanceIdentifier FLOW_PATH = NODE_AUGMENT_PATH.builder() - .child(Table.class,TABLE_KEY) - .child(Flow.class,FLOW_KEY) + private static final InstanceIdentifier LIST11_PATH = TLL_COMPLEX_AUGMENT_PATH.builder() + .child(List1.class,LIST1_KEY) + .child(List11.class,LIST11_KEY) .build(); @@ -53,12 +49,12 @@ public class DeleteNestedAugmentationListenParentTest extends AbstractDataServic public void deleteChildListenParent() throws InterruptedException, ExecutionException { DataModificationTransaction initTx = baDataService.beginTransaction(); - initTx.putOperationalData(FLOW_PATH, flow()); + initTx.putOperationalData(LIST11_PATH, createList11()); initTx.commit().get(); final SettableFuture, DataObject>> event = SettableFuture.create(); - baDataService.registerDataChangeListener(FLOW_PATH, new DataChangeListener() { + baDataService.registerDataChangeListener(LIST11_PATH, new DataChangeListener() { @Override public void onDataChanged(final DataChangeEvent, DataObject> change) { @@ -67,23 +63,19 @@ public class DeleteNestedAugmentationListenParentTest extends AbstractDataServic }); DataModificationTransaction deleteTx = baDataService.beginTransaction(); - deleteTx.removeOperationalData(FLOW_PATH.augmentation(FlowStatisticsData.class)); + deleteTx.removeOperationalData(LIST11_PATH.augmentation(List11SimpleAugment.class)); deleteTx.commit().get(); DataChangeEvent, DataObject> receivedEvent = event.get(); - assertFalse(receivedEvent.getRemovedOperationalData().contains(NODE_AUGMENT_PATH)); + assertFalse(receivedEvent.getRemovedOperationalData().contains(TLL_COMPLEX_AUGMENT_PATH)); } - private Flow flow() { - FlowBuilder builder = new FlowBuilder() - .setKey(FLOW_KEY) - .addAugmentation(FlowStatisticsData.class,new FlowStatisticsDataBuilder() - .setFlowStatistics(new FlowStatisticsBuilder().build()) - .build()) - .setBarrier(true) - .setMatch(new MatchBuilder() - .build()) - ; + private List11 createList11() { + List11Builder builder = new List11Builder() + .setKey(LIST11_KEY) + .addAugmentation(List11SimpleAugment.class,new List11SimpleAugmentBuilder() + .setAttrStr2("bad").build()) + .setAttrStr("good"); return builder.build(); } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/FlagsSerializationTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/FlagsSerializationTest.java index 7143352c1f..9ad46bb99f 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/FlagsSerializationTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/FlagsSerializationTest.java @@ -11,39 +11,20 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.PopMplsActionCaseBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.pop.mpls.action._case.PopMplsActionBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.types.rev130827.VlanId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.VlanMatchBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.vlan.match.fields.VlanIdBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.BitFlags; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.TllComplexAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11Builder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; @@ -54,41 +35,36 @@ import com.google.common.collect.ImmutableSet; @SuppressWarnings("deprecation") public class FlagsSerializationTest extends AbstractDataServiceTest { - - private static final String FLOW_ID = "1234"; - private static final short TABLE_ID = (short)0; - private static final String NODE_ID = "node:1"; - - private static final NodeKey NODE_KEY = new NodeKey(new NodeId(NODE_ID)); - private static final FlowKey FLOW_KEY = new FlowKey(new FlowId(FLOW_ID)); - private static final TableKey TABLE_KEY = new TableKey(TABLE_ID); - - private static final InstanceIdentifier NODE_INSTANCE_ID_BA = InstanceIdentifier.builder(Nodes.class) // - .child(Node.class, NODE_KEY).toInstance(); - - private static final InstanceIdentifier FLOW_INSTANCE_ID_BA = // - NODE_INSTANCE_ID_BA.builder() // - .augmentation(FlowCapableNode.class) - .child(Table.class,TABLE_KEY) - .child(Flow.class, FLOW_KEY) // + private static final TopLevelListKey TLL_KEY = new TopLevelListKey("foo"); + private static final List11Key LIST11_KEY = new List11Key(1234); + private static final List1Key LIST1_KEY = new List1Key("1"); + + private static final InstanceIdentifier TLL_INSTANCE_ID_BA = InstanceIdentifier.builder(Top.class) // + .child(TopLevelList.class, TLL_KEY).toInstance(); + + private static final InstanceIdentifier LIST11_INSTANCE_ID_BA = // + TLL_INSTANCE_ID_BA.builder() // + .augmentation(TllComplexAugment.class) + .child(List1.class,LIST1_KEY) + .child(List11.class, LIST11_KEY) // .toInstance(); - private static final QName FLOW_FLAGS_QNAME = QName.create(Flow.QNAME, "flags"); + private static final QName LIST11_FLAGS_QNAME = QName.create(List11.QNAME, "flags"); @Test public void testIndirectGeneration() throws Exception { - FlowModFlags checkOverlapFlags = new FlowModFlags(true,false,false,false,false); - ImmutableSet domCheckOverlapFlags = ImmutableSet.of("CHECK_OVERLAP"); + BitFlags checkOverlapFlags = new BitFlags(true,false,false,false,false); + ImmutableSet domCheckOverlapFlags = ImmutableSet.of("FLAG_FIVE"); testFlags(checkOverlapFlags,domCheckOverlapFlags); - FlowModFlags allFalseFlags = new FlowModFlags(false,false,false,false,false); + BitFlags allFalseFlags = new BitFlags(false,false,false,false,false); ImmutableSet domAllFalseFlags = ImmutableSet.of(); testFlags(allFalseFlags,domAllFalseFlags); - FlowModFlags allTrueFlags = new FlowModFlags(true,true,true,true,true); - ImmutableSet domAllTrueFlags = ImmutableSet.of("CHECK_OVERLAP","NO_BYT_COUNTS", "NO_PKT_COUNTS", "RESET_COUNTS", "SEND_FLOW_REM"); + BitFlags allTrueFlags = new BitFlags(true,true,true,true,true); + ImmutableSet domAllTrueFlags = ImmutableSet.of("FLAG_ONE","FLAG_TWO","FLAG_THREE","FLAG_FOUR","FLAG_FIVE"); testFlags(allTrueFlags,domAllTrueFlags); testFlags(null,null); @@ -97,14 +73,14 @@ public class FlagsSerializationTest extends AbstractDataServiceTest { } - private void testFlags(final FlowModFlags flagsToTest, final ImmutableSet domFlags) throws Exception { - Flow flow = createFlow(flagsToTest); - assertNotNull(flow); + private void testFlags(final BitFlags flagsToTest, final ImmutableSet domFlags) throws Exception { + List11 list11 = createList11(flagsToTest); + assertNotNull(list11); - CompositeNode domFlow = biDataService.readConfigurationData(mappingService.toDataDom(FLOW_INSTANCE_ID_BA)); + CompositeNode domList11 = biDataService.readConfigurationData(mappingService.toDataDom(LIST11_INSTANCE_ID_BA)); - assertNotNull(domFlow); - org.opendaylight.yangtools.yang.data.api.Node readedFlags = domFlow.getFirstSimpleByName(FLOW_FLAGS_QNAME); + assertNotNull(domList11); + org.opendaylight.yangtools.yang.data.api.Node readedFlags = domList11.getFirstSimpleByName(LIST11_FLAGS_QNAME); if(domFlags != null) { assertNotNull(readedFlags); @@ -112,55 +88,30 @@ public class FlagsSerializationTest extends AbstractDataServiceTest { } else { assertNull(readedFlags); } - assertEquals(flagsToTest, flow.getFlags()); + assertEquals(flagsToTest, list11.getFlags()); DataModificationTransaction transaction = baDataService.beginTransaction(); - transaction.removeConfigurationData(FLOW_INSTANCE_ID_BA); + transaction.removeConfigurationData(LIST11_INSTANCE_ID_BA); RpcResult result = transaction.commit().get(); assertEquals(TransactionStatus.COMMITED, result.getResult()); } - private Flow createFlow(final FlowModFlags flagsToTest) throws Exception { + private List11 createList11(final BitFlags flagsToTest) throws Exception { DataModificationTransaction modification = baDataService.beginTransaction(); - FlowBuilder flow = new FlowBuilder(); - MatchBuilder match = new MatchBuilder(); - VlanMatchBuilder vlanBuilder = new VlanMatchBuilder(); - VlanIdBuilder vlanIdBuilder = new VlanIdBuilder(); - VlanId vlanId = new VlanId(10); - vlanBuilder.setVlanId(vlanIdBuilder.setVlanId(vlanId).build()); - match.setVlanMatch(vlanBuilder.build()); - - flow.setKey(FLOW_KEY); - flow.setMatch(match.build()); - - flow.setFlags(flagsToTest); - - InstructionsBuilder instructions = new InstructionsBuilder(); - InstructionBuilder instruction = new InstructionBuilder(); - - instruction.setOrder(10); - ApplyActionsBuilder applyActions = new ApplyActionsBuilder(); - List actionList = new ArrayList<>(); - PopMplsActionBuilder popMplsAction = new PopMplsActionBuilder(); - popMplsAction.setEthernetType(34); - actionList.add(new ActionBuilder().setAction(new PopMplsActionCaseBuilder().setPopMplsAction(popMplsAction.build()).build()).setOrder(10).build()); - - applyActions.setAction(actionList ); - - instruction.setInstruction(new ApplyActionsCaseBuilder().setApplyActions(applyActions.build()).build()); + List11Builder list11b = new List11Builder(); + list11b.setKey(LIST11_KEY); + list11b.setAttrStr("list:1:1"); - List instructionList = Collections.singletonList(instruction.build()); - instructions.setInstruction(instructionList ); + list11b.setFlags(flagsToTest); - flow.setInstructions(instructions.build()); - modification.putConfigurationData(FLOW_INSTANCE_ID_BA, flow.build()); + modification.putConfigurationData(LIST11_INSTANCE_ID_BA, list11b.build()); RpcResult ret = modification.commit().get(); assertNotNull(ret); assertEquals(TransactionStatus.COMMITED, ret.getResult()); - return (Flow) baDataService.readConfigurationData(FLOW_INSTANCE_ID_BA); + return (List11) baDataService.readConfigurationData(LIST11_INSTANCE_ID_BA); } } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/PutAugmentationTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/PutAugmentationTest.java index 767ccaade3..9bf6c4d291 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/PutAugmentationTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/PutAugmentationTest.java @@ -23,19 +23,18 @@ import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnectorBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.NestedListSimpleAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.NestedListSimpleAugmentBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.TllComplexAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.TllComplexAugmentBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.TreeComplexUsesAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.top.level.list.NestedList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.top.level.list.NestedListBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.top.level.list.NestedListKey; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; @@ -47,35 +46,35 @@ import com.google.common.util.concurrent.SettableFuture; @SuppressWarnings("deprecation") public class PutAugmentationTest extends AbstractDataServiceTest implements DataChangeListener { - private static final QName NODE_ID_QNAME = QName.create(Node.QNAME, "id"); - private static final String NODE_ID = "openflow:1"; + private static final QName TLL_NAME_QNAME = QName.create(TopLevelList.QNAME, "name"); + private static final String TLL_NAME = "foo"; - private static final NodeKey NODE_KEY = new NodeKey(new NodeId(NODE_ID)); + private static final TopLevelListKey TLL_KEY = new TopLevelListKey(TLL_NAME); - private static final Map NODE_KEY_BI = Collections. singletonMap(NODE_ID_QNAME, - NODE_ID); + private static final Map TLL_KEY_BI = Collections. singletonMap(TLL_NAME_QNAME, + TLL_NAME); - private static final InstanceIdentifier NODES_INSTANCE_ID_BA = InstanceIdentifier.builder(Nodes.class) // + private static final InstanceIdentifier TOP_INSTANCE_ID_BA = InstanceIdentifier.builder(Top.class) // .toInstance(); - private static final InstanceIdentifier NODE_INSTANCE_ID_BA = // - NODES_INSTANCE_ID_BA.builder() // - .child(Node.class, NODE_KEY).toInstance(); + private static final InstanceIdentifier TLL_INSTANCE_ID_BA = // + TOP_INSTANCE_ID_BA.builder() // + .child(TopLevelList.class, TLL_KEY).toInstance(); - private static final InstanceIdentifier ALL_FLOW_CAPABLE_NODES = // - NODES_INSTANCE_ID_BA.builder() // - .child(Node.class) // - .augmentation(FlowCapableNode.class) // + private static final InstanceIdentifier ALL_TCA = // + TOP_INSTANCE_ID_BA.builder() // + .child(TopLevelList.class) // + .augmentation(TllComplexAugment.class) // .build(); - private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier NODE_INSTANCE_ID_BI = // + private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier TLL_INSTANCE_ID_BI = // org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.builder() // - .node(Nodes.QNAME) // - .nodeWithKey(Node.QNAME, NODE_KEY_BI) // + .node(Top.QNAME) // + .nodeWithKey(TopLevelList.QNAME, TLL_KEY_BI) // .toInstance(); - private static final InstanceIdentifier FLOW_AUGMENTATION_PATH = - NODE_INSTANCE_ID_BA.builder() // - .augmentation(FlowCapableNode.class) // + private static final InstanceIdentifier TCA_AUGMENTATION_PATH = + TLL_INSTANCE_ID_BA.builder() // + .augmentation(TllComplexAugment.class) // .build(); private SettableFuture, DataObject>> lastReceivedChangeEvent; @@ -89,32 +88,28 @@ public class PutAugmentationTest extends AbstractDataServiceTest implements Data @Ignore public void putNodeAndAugmentation() throws Exception { lastReceivedChangeEvent = SettableFuture.create(); - baDataService.registerDataChangeListener(ALL_FLOW_CAPABLE_NODES, this); + baDataService.registerDataChangeListener(ALL_TCA, this); - NodeBuilder nodeBuilder = new NodeBuilder(); - nodeBuilder.setId(new NodeId(NODE_ID)); - nodeBuilder.setKey(NODE_KEY); + TopLevelListBuilder nodeBuilder = new TopLevelListBuilder(); + nodeBuilder.setKey(TLL_KEY); DataModificationTransaction baseTransaction = baDataService.beginTransaction(); - baseTransaction.putOperationalData(NODE_INSTANCE_ID_BA, nodeBuilder.build()); + baseTransaction.putOperationalData(TLL_INSTANCE_ID_BA, nodeBuilder.build()); RpcResult result = baseTransaction.commit().get(); assertEquals(TransactionStatus.COMMITED, result.getResult()); - Node node = (Node) baDataService.readOperationalData(NODE_INSTANCE_ID_BA); - assertNotNull(node); - assertEquals(NODE_KEY, node.getKey()); - - FlowCapableNodeBuilder fnub = new FlowCapableNodeBuilder(); - fnub.setHardware("Hardware Foo"); - fnub.setManufacturer("Manufacturer Foo"); - fnub.setSerialNumber("Serial Foo"); - fnub.setDescription("Description Foo"); - fnub.setSoftware("JUnit emulated"); - FlowCapableNode fnu = fnub.build(); - InstanceIdentifier augmentIdentifier = NODE_INSTANCE_ID_BA - .augmentation(FlowCapableNode.class); + TopLevelList tll = (TopLevelList) baDataService.readOperationalData(TLL_INSTANCE_ID_BA); + assertNotNull(tll); + assertEquals(TLL_KEY, tll.getKey()); + + TllComplexAugmentBuilder tcab = new TllComplexAugmentBuilder(); + tcab.setAttrStr1("FooFoo"); + tcab.setAttrStr2("BarBar"); + TllComplexAugment tca = tcab.build(); + InstanceIdentifier augmentIdentifier = TLL_INSTANCE_ID_BA + .augmentation(TreeComplexUsesAugment.class); DataModificationTransaction augmentedTransaction = baDataService.beginTransaction(); - augmentedTransaction.putOperationalData(augmentIdentifier, fnu); + augmentedTransaction.putOperationalData(augmentIdentifier, tca); lastReceivedChangeEvent = SettableFuture.create(); @@ -123,102 +118,98 @@ public class PutAugmentationTest extends AbstractDataServiceTest implements Data DataChangeEvent, DataObject> potential = lastReceivedChangeEvent.get(1000,TimeUnit.MILLISECONDS); assertNotNull(potential); - assertTrue(potential.getCreatedOperationalData().containsKey(FLOW_AUGMENTATION_PATH)); + assertTrue(potential.getCreatedOperationalData().containsKey(TCA_AUGMENTATION_PATH)); lastReceivedChangeEvent = SettableFuture.create(); - Node augmentedNode = (Node) baDataService.readOperationalData(NODE_INSTANCE_ID_BA); - assertNotNull(node); - assertEquals(NODE_KEY, augmentedNode.getKey()); + TopLevelList augmentedTll = (TopLevelList) baDataService.readOperationalData(TLL_INSTANCE_ID_BA); + assertNotNull(tll); + assertEquals(TLL_KEY, augmentedTll.getKey()); System.out.println("Before assertion"); - assertNotNull(augmentedNode.getAugmentation(FlowCapableNode.class)); - FlowCapableNode readedAugmentation = augmentedNode.getAugmentation(FlowCapableNode.class); - assertEquals(fnu.getDescription(), readedAugmentation.getDescription()); - assertBindingIndependentVersion(NODE_INSTANCE_ID_BI); - testNodeRemove(); - assertTrue(lastReceivedChangeEvent.get(1000,TimeUnit.MILLISECONDS).getRemovedOperationalData().contains(FLOW_AUGMENTATION_PATH)); + assertNotNull(augmentedTll.getAugmentation(TllComplexAugment.class)); + TllComplexAugment readedAugmentation = augmentedTll.getAugmentation(TllComplexAugment.class); + assertEquals(tca.getAttrStr2(), readedAugmentation.getAttrStr2()); + assertBindingIndependentVersion(TLL_INSTANCE_ID_BI); + testTllRemove(); + assertTrue(lastReceivedChangeEvent.get(1000,TimeUnit.MILLISECONDS).getRemovedOperationalData().contains(TCA_AUGMENTATION_PATH)); } @Test @Ignore public void putNodeWithAugmentation() throws Exception { lastReceivedChangeEvent = SettableFuture.create(); - baDataService.registerDataChangeListener(ALL_FLOW_CAPABLE_NODES, this); - - NodeBuilder nodeBuilder = new NodeBuilder(); - nodeBuilder.setId(new NodeId(NODE_ID)); - nodeBuilder.setKey(NODE_KEY); - FlowCapableNodeBuilder fnub = new FlowCapableNodeBuilder(); - fnub.setHardware("Hardware Foo"); - fnub.setManufacturer("Manufacturer Foo"); - fnub.setSerialNumber("Serial Foo"); - fnub.setDescription("Description Foo"); - fnub.setSoftware("JUnit emulated"); - FlowCapableNode fnu = fnub.build(); - - nodeBuilder.addAugmentation(FlowCapableNode.class, fnu); + baDataService.registerDataChangeListener(ALL_TCA, this); + + TopLevelListBuilder nodeBuilder = new TopLevelListBuilder(); + nodeBuilder.setKey(TLL_KEY); + TllComplexAugmentBuilder tcab = new TllComplexAugmentBuilder(); + tcab.setAttrStr1("FooFoo"); + tcab.setAttrStr2("BarBar"); + TllComplexAugment tca = tcab.build(); + + nodeBuilder.addAugmentation(TreeComplexUsesAugment.class, tca); DataModificationTransaction baseTransaction = baDataService.beginTransaction(); - baseTransaction.putOperationalData(NODE_INSTANCE_ID_BA, nodeBuilder.build()); + baseTransaction.putOperationalData(TLL_INSTANCE_ID_BA, nodeBuilder.build()); RpcResult result = baseTransaction.commit().get(); DataChangeEvent, DataObject> potential = lastReceivedChangeEvent.get(1000,TimeUnit.MILLISECONDS); assertNotNull(potential); - assertTrue(potential.getCreatedOperationalData().containsKey(FLOW_AUGMENTATION_PATH)); + assertTrue(potential.getCreatedOperationalData().containsKey(TCA_AUGMENTATION_PATH)); lastReceivedChangeEvent = SettableFuture.create(); assertEquals(TransactionStatus.COMMITED, result.getResult()); - FlowCapableNode readedAugmentation = (FlowCapableNode) baDataService.readOperationalData( - NODE_INSTANCE_ID_BA.augmentation(FlowCapableNode.class)); + TllComplexAugment readedAugmentation = (TllComplexAugment) baDataService.readOperationalData( + TLL_INSTANCE_ID_BA.augmentation(TllComplexAugment.class)); assertNotNull(readedAugmentation); - assertEquals(fnu.getHardware(), readedAugmentation.getHardware()); + assertEquals(tca.getAttrStr1(), readedAugmentation.getAttrStr1()); testPutNodeConnectorWithAugmentation(); lastReceivedChangeEvent = SettableFuture.create(); - testNodeRemove(); + testTllRemove(); - assertTrue(lastReceivedChangeEvent.get(1000,TimeUnit.MILLISECONDS).getRemovedOperationalData().contains(FLOW_AUGMENTATION_PATH)); + assertTrue(lastReceivedChangeEvent.get(1000,TimeUnit.MILLISECONDS).getRemovedOperationalData().contains(TCA_AUGMENTATION_PATH)); } private void testPutNodeConnectorWithAugmentation() throws Exception { - NodeConnectorKey ncKey = new NodeConnectorKey(new NodeConnectorId("test:0:0")); - InstanceIdentifier ncPath = NODE_INSTANCE_ID_BA - .child(NodeConnector.class, ncKey); - InstanceIdentifier ncAugmentPath = ncPath - .augmentation(FlowCapableNodeConnector.class); + NestedListKey ncKey = new NestedListKey("test:0:0"); + InstanceIdentifier ncPath = TLL_INSTANCE_ID_BA + .child(NestedList.class, ncKey); + InstanceIdentifier ncAugmentPath = ncPath + .augmentation(NestedListSimpleAugment.class); - NodeConnectorBuilder nc = new NodeConnectorBuilder(); + NestedListBuilder nc = new NestedListBuilder(); nc.setKey(ncKey); - FlowCapableNodeConnectorBuilder fncb = new FlowCapableNodeConnectorBuilder(); - fncb.setName("Baz"); - nc.addAugmentation(FlowCapableNodeConnector.class, fncb.build()); + NestedListSimpleAugmentBuilder fncb = new NestedListSimpleAugmentBuilder(); + fncb.setType("Baz"); + nc.addAugmentation(NestedListSimpleAugment.class, fncb.build()); DataModificationTransaction baseTransaction = baDataService.beginTransaction(); baseTransaction.putOperationalData(ncPath, nc.build()); RpcResult result = baseTransaction.commit().get(); assertEquals(TransactionStatus.COMMITED, result.getResult()); - FlowCapableNodeConnector readedAugmentation = (FlowCapableNodeConnector) baDataService + NestedListSimpleAugment readedAugmentation = (NestedListSimpleAugment) baDataService .readOperationalData(ncAugmentPath); assertNotNull(readedAugmentation); - assertEquals(fncb.getName(), readedAugmentation.getName()); + assertEquals(fncb.getType(), readedAugmentation.getType()); } - private void testNodeRemove() throws Exception { + private void testTllRemove() throws Exception { DataModificationTransaction transaction = baDataService.beginTransaction(); - transaction.removeOperationalData(NODE_INSTANCE_ID_BA); + transaction.removeOperationalData(TLL_INSTANCE_ID_BA); RpcResult result = transaction.commit().get(); assertEquals(TransactionStatus.COMMITED, result.getResult()); - Node node = (Node) baDataService.readOperationalData(NODE_INSTANCE_ID_BA); - assertNull(node); + TopLevelList tll = (TopLevelList) baDataService.readOperationalData(TLL_INSTANCE_ID_BA); + assertNull(tll); } - private void assertBindingIndependentVersion(final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier nodeId) { - CompositeNode node = biDataService.readOperationalData(nodeId); - assertNotNull(node); + private void assertBindingIndependentVersion(final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier tllId) { + CompositeNode tll = biDataService.readOperationalData(tllId); + assertNotNull(tll); } @Override diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java index b09ba39a65..0f9051d41c 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentListenAugmentTest.java @@ -18,13 +18,13 @@ import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.TreeComplexUsesAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.TreeComplexUsesAugmentBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.complex.from.grouping.ContainerWithUsesBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; @@ -34,17 +34,17 @@ import com.google.common.util.concurrent.SettableFuture; @SuppressWarnings("deprecation") public class WriteParentListenAugmentTest extends AbstractDataServiceTest { - private static final String NODE_ID = "node:1"; + private static final String TLL_NAME = "foo"; - private static final NodeKey NODE_KEY = new NodeKey(new NodeId(NODE_ID)); - private static final InstanceIdentifier NODE_INSTANCE_ID_BA = InstanceIdentifier.builder(Nodes.class) // - .child(Node.class, NODE_KEY).toInstance(); + private static final TopLevelListKey TLL_KEY = new TopLevelListKey(TLL_NAME); + private static final InstanceIdentifier TLL_INSTANCE_ID_BA = InstanceIdentifier.builder(Top.class) // + .child(TopLevelList.class, TLL_KEY).toInstance(); - private static final InstanceIdentifier AUGMENT_WILDCARDED_PATH = InstanceIdentifier - .builder(Nodes.class).child(Node.class).augmentation(FlowCapableNode.class).toInstance(); + private static final InstanceIdentifier AUGMENT_WILDCARDED_PATH = InstanceIdentifier + .builder(Top.class).child(TopLevelList.class).augmentation(TreeComplexUsesAugment.class).toInstance(); - private static final InstanceIdentifier AUGMENT_NODE_PATH = InstanceIdentifier - .builder(Nodes.class).child(Node.class, NODE_KEY).augmentation(FlowCapableNode.class).toInstance(); + private static final InstanceIdentifier AUGMENT_TLL_PATH = InstanceIdentifier + .builder(Top.class).child(TopLevelList.class, TLL_KEY).augmentation(TreeComplexUsesAugment.class).toInstance(); @Test public void writeNodeListenAugment() throws Exception { @@ -62,29 +62,29 @@ public class WriteParentListenAugmentTest extends AbstractDataServiceTest { DataModificationTransaction modification = baDataService.beginTransaction(); - Node node = new NodeBuilder() // - .setKey(NODE_KEY) // - .addAugmentation(FlowCapableNode.class, flowCapableNode("one")).build(); - modification.putOperationalData(NODE_INSTANCE_ID_BA, node); + TopLevelList tll = new TopLevelListBuilder() // + .setKey(TLL_KEY) // + .addAugmentation(TreeComplexUsesAugment.class, treeComplexUsesAugment("one")).build(); + modification.putOperationalData(TLL_INSTANCE_ID_BA, tll); modification.commit().get(); DataChangeEvent, DataObject> receivedEvent = event.get(1000, TimeUnit.MILLISECONDS); - assertTrue(receivedEvent.getCreatedOperationalData().containsKey(AUGMENT_NODE_PATH)); + assertTrue(receivedEvent.getCreatedOperationalData().containsKey(AUGMENT_TLL_PATH)); dclRegistration.close(); DataModificationTransaction mod2 = baDataService.beginTransaction(); - mod2.putOperationalData(AUGMENT_NODE_PATH, flowCapableNode("two")); + mod2.putOperationalData(AUGMENT_TLL_PATH, treeComplexUsesAugment("two")); mod2.commit().get(); - FlowCapableNode readedAug = (FlowCapableNode) baDataService.readOperationalData(AUGMENT_NODE_PATH); - assertEquals("two", readedAug.getDescription()); + TreeComplexUsesAugment readedAug = (TreeComplexUsesAugment) baDataService.readOperationalData(AUGMENT_TLL_PATH); + assertEquals("two", readedAug.getContainerWithUses().getLeafFromGrouping()); } - private FlowCapableNode flowCapableNode(final String description) { - return new FlowCapableNodeBuilder() // - .setDescription(description) // + private TreeComplexUsesAugment treeComplexUsesAugment(final String value) { + return new TreeComplexUsesAugmentBuilder() // + .setContainerWithUses(new ContainerWithUsesBuilder().setLeafFromGrouping(value).build()) // .build(); } } \ No newline at end of file diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentReadChildTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentReadChildTest.java index ad02d9a6f6..7941f4d4ae 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentReadChildTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/bugfix/WriteParentReadChildTest.java @@ -16,24 +16,16 @@ import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.types.rev130827.VlanId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.VlanMatchBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.vlan.match.fields.VlanIdBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.TllComplexAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1Builder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11Builder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; @@ -43,23 +35,23 @@ import com.google.common.collect.ImmutableList; @SuppressWarnings("deprecation") public class WriteParentReadChildTest extends AbstractDataServiceTest { - private static final String FLOW_ID = "1234"; - private static final short TABLE_ID = (short) 0; - private static final String NODE_ID = "node:1"; + private static final int LIST11_ID = 1234; + private static final String LIST1_NAME = "bar"; + private static final String TLL_NAME = "foo"; - private static final NodeKey NODE_KEY = new NodeKey(new NodeId(NODE_ID)); - private static final FlowKey FLOW_KEY = new FlowKey(new FlowId(FLOW_ID)); - private static final TableKey TABLE_KEY = new TableKey(TABLE_ID); + private static final TopLevelListKey TLL_KEY = new TopLevelListKey(TLL_NAME); + private static final List11Key LIST11_KEY = new List11Key(LIST11_ID); + private static final List1Key LIST1_KEY = new List1Key(LIST1_NAME); - private static final InstanceIdentifier NODE_INSTANCE_ID_BA = InstanceIdentifier.builder(Nodes.class) // - .child(Node.class, NODE_KEY).toInstance(); + private static final InstanceIdentifier TLL_INSTANCE_ID_BA = InstanceIdentifier.builder(Top.class) // + .child(TopLevelList.class, TLL_KEY).toInstance(); - private static final InstanceIdentifier
TABLE_INSTANCE_ID_BA = // - NODE_INSTANCE_ID_BA.builder() // - .augmentation(FlowCapableNode.class).child(Table.class, TABLE_KEY).build(); + private static final InstanceIdentifier LIST1_INSTANCE_ID_BA = // + TLL_INSTANCE_ID_BA.builder() // + .augmentation(TllComplexAugment.class).child(List1.class, LIST1_KEY).build(); - private static final InstanceIdentifier FLOW_INSTANCE_ID_BA = // - TABLE_INSTANCE_ID_BA.child(Flow.class, FLOW_KEY); + private static final InstanceIdentifier LIST11_INSTANCE_ID_BA = // + LIST1_INSTANCE_ID_BA.child(List11.class, LIST11_KEY); /** * * The scenario tests writing parent node, which also contains child items @@ -70,43 +62,33 @@ public class WriteParentReadChildTest extends AbstractDataServiceTest { * @throws Exception */ @Test - public void writeTableReadFlow() throws Exception { + public void writeParentReadChild() throws Exception { DataModificationTransaction modification = baDataService.beginTransaction(); - Flow flow = new FlowBuilder() // - .setKey(FLOW_KEY) // - .setMatch(new MatchBuilder() // - .setVlanMatch(new VlanMatchBuilder() // - .setVlanId(new VlanIdBuilder() // - .setVlanId(new VlanId(10)) // - .build()) // - .build()) // - .build()) // - .setInstructions(new InstructionsBuilder() // - .setInstruction(ImmutableList.builder() // - .build()) // - .build()) // + List11 list11 = new List11Builder() // + .setKey(LIST11_KEY) // + .setAttrStr("primary") .build(); - Table table = new TableBuilder() - .setKey(TABLE_KEY) - .setFlow(ImmutableList.of(flow)) + List1 list1 = new List1Builder() + .setKey(LIST1_KEY) + .setList11(ImmutableList.of(list11)) .build(); - modification.putConfigurationData(TABLE_INSTANCE_ID_BA, table); + modification.putConfigurationData(LIST1_INSTANCE_ID_BA, list1); RpcResult ret = modification.commit().get(); assertNotNull(ret); assertEquals(TransactionStatus.COMMITED, ret.getResult()); - DataObject readedTable = baDataService.readConfigurationData(TABLE_INSTANCE_ID_BA); - assertNotNull("Readed table should not be nul.", readedTable); - assertTrue(readedTable instanceof Table); + DataObject readList1 = baDataService.readConfigurationData(LIST1_INSTANCE_ID_BA); + assertNotNull("Readed table should not be nul.", readList1); + assertTrue(readList1 instanceof List1); - DataObject readedFlow = baDataService.readConfigurationData(FLOW_INSTANCE_ID_BA); - assertNotNull("Readed flow should not be null.",readedFlow); - assertTrue(readedFlow instanceof Flow); - assertEquals(flow, readedFlow); + DataObject readList11 = baDataService.readConfigurationData(LIST11_INSTANCE_ID_BA); + assertNotNull("Readed flow should not be null.",readList11); + assertTrue(readList11 instanceof List11); + assertEquals(list11, readList11); } } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/BrokerIntegrationTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/BrokerIntegrationTest.java index 481a7ddfa2..48027114d7 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/BrokerIntegrationTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/BrokerIntegrationTest.java @@ -17,30 +17,37 @@ import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; -// FIXME: Migrate to use new Data Broker APIs +/** + * FIXME: Migrate to use new Data Broker APIs + */ @SuppressWarnings("deprecation") public class BrokerIntegrationTest extends AbstractDataServiceTest { + private static final TopLevelListKey TLL_FOO_KEY = new TopLevelListKey("foo"); + private static final TopLevelListKey TLL_BAR_KEY = new TopLevelListKey("bar"); + private static final TopLevelListKey TLL_BAZ_KEY = new TopLevelListKey("baz"); + private static final InstanceIdentifier TOP_PATH = InstanceIdentifier.builder(Top.class).build(); + private static final InstanceIdentifier FOO_PATH = TOP_PATH.child(TopLevelList.class, TLL_FOO_KEY); + private static final InstanceIdentifier BAR_PATH = TOP_PATH.child(TopLevelList.class, TLL_BAR_KEY); + private static final InstanceIdentifier BAZ_PATH = TOP_PATH.child(TopLevelList.class, TLL_BAZ_KEY); + @Test public void simpleModifyOperation() throws Exception { - NodeRef node1 = createNodeRef("0"); - DataObject node = baDataService.readConfigurationData(node1.getValue()); - assertNull(node); - Node nodeData1 = createNode("0"); + DataObject tllFoo = baDataService.readConfigurationData(FOO_PATH); + assertNull(tllFoo); + TopLevelList tllFooData = createTll(TLL_FOO_KEY); DataModificationTransaction transaction = baDataService.beginTransaction(); - transaction.putConfigurationData(node1.getValue(), nodeData1); + transaction.putConfigurationData(FOO_PATH, tllFooData); Future> commitResult = transaction.commit(); assertNotNull(commitResult); @@ -50,29 +57,26 @@ public class BrokerIntegrationTest extends AbstractDataServiceTest { assertNotNull(result.getResult()); assertEquals(TransactionStatus.COMMITED, result.getResult()); - Node readedData = (Node) baDataService.readConfigurationData(node1.getValue()); + TopLevelList readedData = (TopLevelList) baDataService.readConfigurationData(FOO_PATH); assertNotNull(readedData); - assertEquals(nodeData1.getKey(), readedData.getKey()); + assertEquals(tllFooData.getKey(), readedData.getKey()); - NodeRef nodeFoo = createNodeRef("foo"); - NodeRef nodeBar = createNodeRef("bar"); - Node nodeFooData = createNode("foo"); - Node nodeBarData = createNode("bar"); + TopLevelList nodeBarData = createTll(TLL_BAR_KEY); + TopLevelList nodeBazData = createTll(TLL_BAZ_KEY); DataModificationTransaction insertMoreTr = baDataService.beginTransaction(); - insertMoreTr.putConfigurationData(nodeFoo.getValue(), nodeFooData); - insertMoreTr.putConfigurationData(nodeBar.getValue(), nodeBarData); + insertMoreTr.putConfigurationData(BAR_PATH, nodeBarData); + insertMoreTr.putConfigurationData(BAZ_PATH, nodeBazData); RpcResult result2 = insertMoreTr.commit().get(); assertNotNull(result2); assertNotNull(result2.getResult()); assertEquals(TransactionStatus.COMMITED, result.getResult()); - Nodes allNodes = (Nodes) baDataService.readConfigurationData(InstanceIdentifier.builder(Nodes.class) - .toInstance()); - assertNotNull(allNodes); - assertNotNull(allNodes.getNode()); - assertEquals(3, allNodes.getNode().size()); + Top top = (Top) baDataService.readConfigurationData(TOP_PATH); + assertNotNull(top); + assertNotNull(top.getTopLevelList()); + assertEquals(3, top.getTopLevelList().size()); /** * We create transaction no 2 @@ -85,7 +89,7 @@ public class BrokerIntegrationTest extends AbstractDataServiceTest { * We remove node 1 * */ - removalTransaction.removeConfigurationData(node1.getValue()); + removalTransaction.removeConfigurationData(BAR_PATH); /** * We commit transaction @@ -99,21 +103,13 @@ public class BrokerIntegrationTest extends AbstractDataServiceTest { assertNotNull(result3.getResult()); assertEquals(TransactionStatus.COMMITED, result2.getResult()); - DataObject readedData2 = baDataService.readConfigurationData(node1.getValue()); + DataObject readedData2 = baDataService.readConfigurationData(BAR_PATH); assertNull(readedData2); } - private static NodeRef createNodeRef(final String string) { - NodeKey key = new NodeKey(new NodeId(string)); - InstanceIdentifier path = InstanceIdentifier.builder(Nodes.class).child(Node.class, key) - .toInstance(); - return new NodeRef(path); - } - - private static Node createNode(final String string) { - NodeBuilder ret = new NodeBuilder(); - ret.setId(new NodeId(string)); - ret.setKey(new NodeKey(ret.getId())); + private static TopLevelList createTll(final TopLevelListKey key) { + TopLevelListBuilder ret = new TopLevelListBuilder(); + ret.setKey(key); return ret.build(); } } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/ChangeOriginatedInDomBrokerTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/ChangeOriginatedInDomBrokerTest.java index a3b0819501..25b159b43b 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/ChangeOriginatedInDomBrokerTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/ChangeOriginatedInDomBrokerTest.java @@ -11,9 +11,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -23,34 +21,17 @@ import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent; import org.opendaylight.controller.sal.binding.api.data.DataChangeListener; import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest; import org.opendaylight.controller.sal.core.api.data.DataModificationTransaction; -import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpVersion; -import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.DecNwTtlCaseBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.dec.nw.ttl._case.DecNwTtl; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.dec.nw.ttl._case.DecNwTtlBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.IpMatchBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.Ipv4Match; -import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.Ipv4MatchBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.TllComplexAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1Builder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11Builder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List12Builder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List12Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; @@ -59,83 +40,79 @@ import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.SettableFuture; -// FIXME: Migrate to use new Data Broker APIs +/** + * FIXME: Migrate to use new Data Broker APIs + */ @SuppressWarnings("deprecation") public class ChangeOriginatedInDomBrokerTest extends AbstractDataServiceTest { - private static final Logger LOG = LoggerFactory.getLogger(ChangeOriginatedInDomBrokerTest.class); - - private static final QName NODE_ID_QNAME = QName.create(Node.QNAME, "id"); - private static final QName FLOW_ID_QNAME = QName.create(Flow.QNAME, "id"); - private static final QName TABLE_ID_QNAME = QName.create(Table.QNAME, "id"); + protected static final Logger LOG = LoggerFactory.getLogger(ChangeOriginatedInDomBrokerTest.class); - private static final String NODE_ID = "node:1"; - private static final FlowId FLOW_ID = new FlowId("1234"); - private static final Short TABLE_ID = Short.valueOf((short) 0); + private static final QName TLL_NAME_QNAME = QName.create(TopLevelList.QNAME, "name"); + private static final QName LIST1_ATTR_STR_QNAME = QName.create(List1.QNAME, "attr-str"); - private static final NodeKey NODE_KEY = new NodeKey(new NodeId(NODE_ID)); - private static final FlowKey FLOW_KEY = new FlowKey(FLOW_ID); + private static final String TLL_NAME = "1"; + private static final int LIST11_ATTR_INT = 1234; + private static final String LIST1_ATTR_STR = "foo:foo"; - private final SettableFuture, DataObject>> modificationCapture = SettableFuture.create(); + private static final TopLevelListKey TLL_KEY = new TopLevelListKey(TLL_NAME); + private static final List1Key LIST1_KEY = new List1Key(LIST1_ATTR_STR); + private static final List11Key LIST11_KEY = new List11Key(LIST11_ATTR_INT); - private static final Map NODE_KEY_BI = Collections. singletonMap(NODE_ID_QNAME, - NODE_ID); + protected final SettableFuture, DataObject>> modificationCapture = SettableFuture.create(); - private static final InstanceIdentifier NODE_INSTANCE_ID_BA = InstanceIdentifier.builder(Nodes.class) // - .child(Node.class, NODE_KEY).toInstance(); + private static final Map TLL_KEY_BI = Collections. singletonMap(TLL_NAME_QNAME, + TLL_NAME); - private static final Map FLOW_KEY_BI = // - ImmutableMap. of(FLOW_ID_QNAME, FLOW_ID.getValue()); + private static final InstanceIdentifier NODE_INSTANCE_ID_BA = InstanceIdentifier.builder(Top.class) // + .child(TopLevelList.class, TLL_KEY).toInstance(); - private static final Map TABLE_KEY_BI = // - ImmutableMap. of(TABLE_ID_QNAME, TABLE_ID);; + private static final Map LIST1_KEY_BI = // + ImmutableMap. of(LIST1_ATTR_STR_QNAME, LIST1_ATTR_STR);; - private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier FLOW_INSTANCE_ID_BI = // + private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier LIST1_INSTANCE_ID_BI = // org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.builder() // - .node(Nodes.QNAME) // - .nodeWithKey(Node.QNAME, NODE_KEY_BI) // - .nodeWithKey(Table.QNAME, TABLE_KEY_BI) // - .nodeWithKey(Flow.QNAME, FLOW_KEY_BI) // + .node(Top.QNAME) // + .nodeWithKey(TopLevelList.QNAME, TLL_KEY_BI) // + .nodeWithKey(List1.QNAME, LIST1_KEY_BI) // .toInstance(); - private static final TableKey TABLE_KEY_BA = new TableKey((short) 0); - private static final InstanceIdentifier FLOWS_PATH_BA = // + private static final InstanceIdentifier LIST1_PATH_BA = // NODE_INSTANCE_ID_BA.builder() // - .augmentation(FlowCapableNode.class) // - .child(Table.class, TABLE_KEY_BA) // - .child(Flow.class) // + .augmentation(TllComplexAugment.class) // + .child(List1.class, LIST1_KEY) // .toInstance(); - private static final InstanceIdentifier FLOW_INSTANCE_ID_BA = // - FLOWS_PATH_BA.firstIdentifierOf(Table.class).child(Flow.class, FLOW_KEY); - @Test public void simpleModifyOperation() throws Exception { - assertNull(biDataService.readConfigurationData(FLOW_INSTANCE_ID_BI)); + assertNull(biDataService.readConfigurationData(LIST1_INSTANCE_ID_BI)); registerChangeListener(); - CompositeNode domflow = createTestFlow(); + CompositeNode domflow = createTestList1(); DataModificationTransaction biTransaction = biDataService.beginTransaction(); - biTransaction.putConfigurationData(FLOW_INSTANCE_ID_BI, domflow); + biTransaction.putConfigurationData(LIST1_INSTANCE_ID_BI, domflow); RpcResult biResult = biTransaction.commit().get(); assertEquals(TransactionStatus.COMMITED, biResult.getResult()); DataChangeEvent, DataObject> event = modificationCapture.get(1000,TimeUnit.MILLISECONDS); assertNotNull(event); LOG.info("Created Configuration :{}",event.getCreatedConfigurationData()); - Flow flow = (Flow) event.getCreatedConfigurationData().get(FLOW_INSTANCE_ID_BA); - assertNotNull(flow); - assertNotNull(flow.getMatch()); + List1 list1 = (List1) event.getCreatedConfigurationData().get(LIST1_PATH_BA); + assertNotNull(list1); + assertNotNull(list1.getAttrStr()); + assertNotNull(list1.getList11()); + assertNotNull(list1.getList12()); assertEquals(TransactionStatus.COMMITED, biResult.getResult()); } private void registerChangeListener() { - baDataService.registerDataChangeListener(FLOWS_PATH_BA, new DataChangeListener() { + baDataService.registerDataChangeListener(LIST1_PATH_BA, new DataChangeListener() { @Override public void onDataChanged(final DataChangeEvent, DataObject> change) { @@ -145,61 +122,18 @@ public class ChangeOriginatedInDomBrokerTest extends AbstractDataServiceTest { }); } - private CompositeNode createTestFlow() { - FlowBuilder flow = new FlowBuilder(); - flow.setKey(FLOW_KEY); - Short tableId = 0; - flow.setTableId(tableId); - MatchBuilder match = new MatchBuilder(); - match.setIpMatch(new IpMatchBuilder().setIpProto(IpVersion.Ipv4).build()); - Ipv4MatchBuilder ipv4Match = new Ipv4MatchBuilder(); - // ipv4Match.setIpv4Destination(new Ipv4Prefix(cliInput.get(4))); - Ipv4Prefix prefix = new Ipv4Prefix("10.0.0.1/24"); - ipv4Match.setIpv4Destination(prefix); - Ipv4Match i4m = ipv4Match.build(); - match.setLayer3Match(i4m); - flow.setMatch(match.build()); - - - - // Create a drop action - /* - * Note: We are mishandling drop actions DropAction dropAction = new - * DropActionBuilder().build(); ActionBuilder ab = new ActionBuilder(); - * ab.setAction(dropAction); - */ - - DecNwTtl decNwTtl = new DecNwTtlBuilder().build(); - ActionBuilder ab = new ActionBuilder(); - ActionKey actionKey = new ActionKey(0); - ab.setKey(actionKey ); - ab.setAction(new DecNwTtlCaseBuilder().setDecNwTtl(decNwTtl).build()); - - // Add our drop action to a list - List actionList = new ArrayList(); - actionList.add(ab.build()); - - // Create an Apply Action - ApplyActionsBuilder aab = new ApplyActionsBuilder(); - aab.setAction(actionList); - - // Wrap our Apply Action in an Instruction - InstructionBuilder ib = new InstructionBuilder(); - ib.setOrder(0); - ib.setInstruction(new ApplyActionsCaseBuilder().setApplyActions(aab.build()).build()); - - // Put our Instruction in a list of Instructions - InstructionsBuilder isb = new InstructionsBuilder(); - List instructions = new ArrayList(); - instructions.add(ib.build()); - isb.setInstruction(instructions); - - // Add our instructions to the flow - flow.setInstructions(isb.build()); - - flow.setPriority(2); - flow.setFlowName("Foo Name"); - CompositeNode domFlow = mappingService.toDataDom(flow.build()); - return domFlow; + private CompositeNode createTestList1() { + List1Builder l1b = new List1Builder(); + List11Builder l11b = new List11Builder(); + List12Builder l12b = new List12Builder(); + l11b.setKey(LIST11_KEY); + l11b.setAttrStr("foo:foo:foo"); + l12b.setKey(new List12Key(321)); + l12b.setAttrStr("foo:foo:bar"); + l1b.setKey(LIST1_KEY); + l1b.setList11(ImmutableList.of(l11b.build())); + l1b.setList12(ImmutableList.of(l12b.build())); + CompositeNode domList1 = mappingService.toDataDom(l1b.build()); + return domList1; } } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerMountPointTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerMountPointTest.java index d87470a5e9..f199d71aa5 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerMountPointTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerMountPointTest.java @@ -7,10 +7,9 @@ */ package org.opendaylight.controller.sal.binding.test.connect.dom; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; -import java.math.BigInteger; import java.util.Collections; import java.util.Map; @@ -23,16 +22,16 @@ import org.opendaylight.controller.sal.binding.test.util.BindingBrokerTestFactor import org.opendaylight.controller.sal.binding.test.util.BindingTestContext; import org.opendaylight.controller.sal.core.api.mount.MountProvisionInstance; import org.opendaylight.controller.sal.core.api.mount.MountProvisionService; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; -import org.opendaylight.yang.gen.v1.urn.opendaylight.group.statistics.rev131111.NodeGroupStatistics; -import org.opendaylight.yang.gen.v1.urn.opendaylight.group.statistics.rev131111.group.statistics.GroupStatistics; -import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.GroupId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group; -import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.GroupKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.List11SimpleAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.TllComplexAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.List1Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.aug.grouping.list1.List11Key; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.top.top.level.list.list1.list1._1.Cont; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.CompositeNode; @@ -43,39 +42,45 @@ import com.google.common.util.concurrent.MoreExecutors; @SuppressWarnings("deprecation") public class CrossBrokerMountPointTest { - private static final QName NODE_ID_QNAME = QName.create(Node.QNAME, "id"); - private static final String NODE_ID = "node:1"; + private static final QName TLL_NAME_QNAME = QName.create(TopLevelList.QNAME, "name"); + private static final String TLL_NAME = "foo:1"; + + private static final TopLevelListKey TLL_KEY = new TopLevelListKey(TLL_NAME); - private static final NodeKey NODE_KEY = new NodeKey(new NodeId(NODE_ID)); + private static final Map TLL_KEY_BI = Collections. singletonMap(TLL_NAME_QNAME, + TLL_NAME); - private static final Map NODE_KEY_BI = Collections. singletonMap(NODE_ID_QNAME, - NODE_ID); + private static final InstanceIdentifier TLL_INSTANCE_ID_BA = InstanceIdentifier.builder(Top.class) // + .child(TopLevelList.class, TLL_KEY).build(); - private static final InstanceIdentifier NODE_INSTANCE_ID_BA = InstanceIdentifier.builder(Nodes.class) // - .child(Node.class, NODE_KEY).toInstance(); - private static GroupKey GROUP_KEY = new GroupKey(new GroupId(0L)); + private static final List1Key LIST1_KEY = new List1Key("foo"); + private static final List11Key LIST11_KEY = new List11Key(1); - private static final InstanceIdentifier GROUP_STATISTICS_ID_BA = NODE_INSTANCE_ID_BA - .builder().augmentation(FlowCapableNode.class) // - .child(Group.class, GROUP_KEY) // - .augmentation(NodeGroupStatistics.class) // - .child(GroupStatistics.class) // - .toInstance(); + private static final InstanceIdentifier AUG_CONT_ID_BA = TLL_INSTANCE_ID_BA + .builder().augmentation(TllComplexAugment.class) // + .child(List1.class, LIST1_KEY) // + .child(List11.class, LIST11_KEY) // + .augmentation(List11SimpleAugment.class) // + .child(Cont.class) // + .build(); - private static final QName AUGMENTED_GROUP_STATISTICS = QName.create(NodeGroupStatistics.QNAME, - GroupStatistics.QNAME.getLocalName()); + private static final QName AUG_CONT = QName.create(List11.QNAME, + Cont.QNAME.getLocalName()); - private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier NODE_INSTANCE_ID_BI = // + private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier TLL_INSTANCE_ID_BI = // org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.builder() // - .node(Nodes.QNAME) // - .nodeWithKey(Node.QNAME, NODE_KEY_BI) // - .toInstance(); + .node(Top.QNAME) // + .nodeWithKey(TopLevelList.QNAME, TLL_KEY_BI) // + .build(); private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier GROUP_STATISTICS_ID_BI = org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier // - .builder(NODE_INSTANCE_ID_BI) - .nodeWithKey(QName.create(FlowCapableNode.QNAME, "group"), QName.create(FlowCapableNode.QNAME, "group-id"), - 0L).node(AUGMENTED_GROUP_STATISTICS).toInstance(); + .builder(TLL_INSTANCE_ID_BI) + .nodeWithKey(QName.create(TllComplexAugment.QNAME, "list1"), QName.create(TllComplexAugment.QNAME, "attr-str"), + LIST1_KEY.getAttrStr()) + .nodeWithKey(QName.create(TllComplexAugment.QNAME, "list1-1"), QName.create(TllComplexAugment.QNAME, "attr-int"), + LIST11_KEY.getAttrInt()) + .node(AUG_CONT).build(); private BindingTestContext testContext; private MountProviderService bindingMountPointService; @@ -102,14 +107,14 @@ public class CrossBrokerMountPointTest { @Test public void testMountPoint() { - testContext.getBindingDataBroker().readOperationalData(NODE_INSTANCE_ID_BA); + testContext.getBindingDataBroker().readOperationalData(TLL_INSTANCE_ID_BA); - MountProvisionInstance domMountPoint = domMountPointService.createMountPoint(NODE_INSTANCE_ID_BI); + MountProvisionInstance domMountPoint = domMountPointService.createMountPoint(TLL_INSTANCE_ID_BI); assertNotNull(domMountPoint); - MountProviderInstance bindingMountPoint = bindingMountPointService.getMountPoint(NODE_INSTANCE_ID_BA); + MountProviderInstance bindingMountPoint = bindingMountPointService.getMountPoint(TLL_INSTANCE_ID_BA); assertNotNull(bindingMountPoint); - final BigInteger packetCount = BigInteger.valueOf(500L); + final Integer attrIntalue = 500; DataReader simpleReader = new DataReader() { @@ -125,9 +130,9 @@ public class CrossBrokerMountPointTest { if (arg0.equals(GROUP_STATISTICS_ID_BI)) { ImmutableCompositeNode data = ImmutableCompositeNode .builder() - .setQName(AUGMENTED_GROUP_STATISTICS) - .addLeaf(QName.create(AUGMENTED_GROUP_STATISTICS, "packet-count"), packetCount) // - .toInstance(); + .setQName(AUG_CONT) + .addLeaf(QName.create(AUG_CONT, "attr-int"), attrIntalue) // + .build(); return data; } @@ -135,10 +140,10 @@ public class CrossBrokerMountPointTest { } }; - domMountPoint.registerOperationalReader(NODE_INSTANCE_ID_BI, simpleReader); + domMountPoint.registerOperationalReader(TLL_INSTANCE_ID_BI, simpleReader); - GroupStatistics data = (GroupStatistics) bindingMountPoint.readOperationalData(GROUP_STATISTICS_ID_BA); + Cont data = (Cont) bindingMountPoint.readOperationalData(AUG_CONT_ID_BA); assertNotNull(data); - assertEquals(packetCount,data.getPacketCount().getValue()); + assertEquals(attrIntalue ,data.getAttrInt()); } } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerRpcTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerRpcTest.java index ba75d578fb..83c2f88376 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerRpcTest.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/CrossBrokerRpcTest.java @@ -12,11 +12,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; -import java.math.BigInteger; -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.Future; - +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -25,19 +24,15 @@ import org.opendaylight.controller.sal.binding.test.util.BindingBrokerTestFactor import org.opendaylight.controller.sal.binding.test.util.BindingTestContext; import org.opendaylight.controller.sal.core.api.RpcImplementation; import org.opendaylight.controller.sal.core.api.RpcProvisionRegistry; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.NodeFlowRemoved; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.transaction.rev131103.TransactionId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeContext; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.KnockKnockInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.KnockKnockInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.KnockKnockOutput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.KnockKnockOutputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.OpendaylightOfMigrationTestModelService; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.rpc.routing.rev140701.TestContext; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; @@ -45,38 +40,31 @@ import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.common.RpcResultBuilder; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; - -import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.Future; public class CrossBrokerRpcTest { - protected RpcProviderRegistry baRpcRegistry; - protected RpcProvisionRegistry biRpcRegistry; + protected RpcProviderRegistry providerRegistry; + protected RpcProvisionRegistry provisionRegistry; private BindingTestContext testContext; private RpcImplementation biRpcInvoker; - private MessageCapturingFlowService flowService; + private MessageCapturingFlowService knockService; - public static final NodeId NODE_A = new NodeId("a"); - public static final NodeId NODE_B = new NodeId("b"); - public static final NodeId NODE_C = new NodeId("c"); - public static final NodeId NODE_D = new NodeId("d"); + public static final TopLevelListKey NODE_A = new TopLevelListKey("a"); + public static final TopLevelListKey NODE_B = new TopLevelListKey("b"); + public static final TopLevelListKey NODE_C = new TopLevelListKey("c"); - private static final QName NODE_ID_QNAME = QName.create(Node.QNAME, "id"); - private static final QName ADD_FLOW_QNAME = QName.create(NodeFlowRemoved.QNAME, "add-flow"); + private static final QName NODE_ID_QNAME = QName.create(TopLevelList.QNAME, "name"); + private static final QName KNOCK_KNOCK_QNAME = QName.create(KnockKnockOutput.QNAME, "knock-knock"); - public static final InstanceIdentifier BA_NODE_A_ID = createBANodeIdentifier(NODE_A); - public static final InstanceIdentifier BA_NODE_B_ID = createBANodeIdentifier(NODE_B); - public static final InstanceIdentifier BA_NODE_C_ID = createBANodeIdentifier(NODE_C); - public static final InstanceIdentifier BA_NODE_D_ID = createBANodeIdentifier(NODE_D); + public static final InstanceIdentifier NODES_PATH = InstanceIdentifier.builder(Top.class).build(); + public static final InstanceIdentifier BA_NODE_A_ID = NODES_PATH.child(TopLevelList.class, NODE_A); + public static final InstanceIdentifier BA_NODE_B_ID = NODES_PATH.child(TopLevelList.class, NODE_B); + public static final InstanceIdentifier BA_NODE_C_ID = NODES_PATH.child(TopLevelList.class, NODE_C); - public static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier BI_NODE_A_ID = createBINodeIdentifier(NODE_A); - public static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier BI_NODE_B_ID = createBINodeIdentifier(NODE_B); public static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier BI_NODE_C_ID = createBINodeIdentifier(NODE_C); - public static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier BI_NODE_D_ID = createBINodeIdentifier(NODE_D); - @Before @@ -87,48 +75,49 @@ public class CrossBrokerRpcTest { testContext = testFactory.getTestContext(); testContext.start(); - baRpcRegistry = testContext.getBindingRpcRegistry(); - biRpcRegistry = testContext.getDomRpcRegistry(); + providerRegistry = testContext.getBindingRpcRegistry(); + provisionRegistry = testContext.getDomRpcRegistry(); biRpcInvoker = testContext.getDomRpcInvoker(); - assertNotNull(baRpcRegistry); - assertNotNull(biRpcRegistry); + assertNotNull(providerRegistry); + assertNotNull(provisionRegistry); - flowService = MessageCapturingFlowService.create(baRpcRegistry); + knockService = MessageCapturingFlowService.create(providerRegistry); } @Test public void bindingRoutedRpcProvider_DomInvokerTest() throws Exception { - flowService// - .registerPath(NodeContext.class, BA_NODE_A_ID) // - .registerPath(NodeContext.class, BA_NODE_B_ID) // - .setAddFlowResult(addFlowResult(true, 10)); + knockService// + .registerPath(TestContext.class, BA_NODE_A_ID) // + .registerPath(TestContext.class, BA_NODE_B_ID) // + .setKnockKnockResult(knockResult(true, "open")); - SalFlowService baFlowInvoker = baRpcRegistry.getRpcService(SalFlowService.class); - assertNotSame(flowService, baFlowInvoker); + OpendaylightOfMigrationTestModelService baKnockInvoker = + providerRegistry.getRpcService(OpendaylightOfMigrationTestModelService.class); + assertNotSame(knockService, baKnockInvoker); - AddFlowInput addFlowA = addFlow(BA_NODE_A_ID) // - .setPriority(100).setBarrier(true).build(); + KnockKnockInput knockKnockA = knockKnock(BA_NODE_A_ID) // + .setQuestion("who's there?").build(); - CompositeNode addFlowDom = toDomRpc(ADD_FLOW_QNAME, addFlowA); - assertNotNull(addFlowDom); - RpcResult domResult = biRpcInvoker.invokeRpc(ADD_FLOW_QNAME, addFlowDom).get(); + CompositeNode knockKnockDom = toDomRpc(KNOCK_KNOCK_QNAME, knockKnockA); + assertNotNull(knockKnockDom); + RpcResult domResult = biRpcInvoker.invokeRpc(KNOCK_KNOCK_QNAME, knockKnockDom).get(); assertNotNull(domResult); assertTrue("DOM result is successful.", domResult.isSuccessful()); - assertTrue("Bidning Add Flow RPC was captured.", flowService.getReceivedAddFlows().containsKey(BA_NODE_A_ID)); - assertEquals(addFlowA, flowService.getReceivedAddFlows().get(BA_NODE_A_ID).iterator().next()); + assertTrue("Bidning Add Flow RPC was captured.", knockService.getReceivedKnocks().containsKey(BA_NODE_A_ID)); + assertEquals(knockKnockA, knockService.getReceivedKnocks().get(BA_NODE_A_ID).iterator().next()); } @Test public void bindingRpcInvoker_DomRoutedProviderTest() throws Exception { - AddFlowOutputBuilder builder = new AddFlowOutputBuilder(); - builder.setTransactionId(new TransactionId(BigInteger.valueOf(10))); - final AddFlowOutput output = builder.build(); - org.opendaylight.controller.sal.core.api.Broker.RoutedRpcRegistration registration = biRpcRegistry.addRoutedRpcImplementation(ADD_FLOW_QNAME, new RpcImplementation() { + KnockKnockOutputBuilder builder = new KnockKnockOutputBuilder(); + builder.setAnswer("open"); + final KnockKnockOutput output = builder.build(); + org.opendaylight.controller.sal.core.api.Broker.RoutedRpcRegistration registration = provisionRegistry.addRoutedRpcImplementation(KNOCK_KNOCK_QNAME, new RpcImplementation() { @Override public Set getSupportedRpcs() { - return ImmutableSet.of(ADD_FLOW_QNAME); + return ImmutableSet.of(KNOCK_KNOCK_QNAME); } @Override @@ -137,12 +126,14 @@ public class CrossBrokerRpcTest { return Futures.immediateFuture(RpcResultBuilder.success(result).build()); } }); - registration.registerPath(NodeContext.QNAME, BI_NODE_C_ID); + registration.registerPath(TestContext.QNAME, BI_NODE_C_ID); + - SalFlowService baFlowInvoker = baRpcRegistry.getRpcService(SalFlowService.class); - Future> baResult = baFlowInvoker.addFlow(addFlow(BA_NODE_C_ID).setPriority(500).build()); + OpendaylightOfMigrationTestModelService baKnockInvoker = + providerRegistry.getRpcService(OpendaylightOfMigrationTestModelService.class); + Future> baResult = baKnockInvoker.knockKnock((knockKnock(BA_NODE_C_ID).setQuestion("Who's there?").build())); assertNotNull(baResult); - assertEquals(output,baResult.get().getResult()); + assertEquals(output, baResult.get().getResult()); } private CompositeNode toDomRpcInput(DataObject addFlowA) { @@ -154,30 +145,26 @@ public class CrossBrokerRpcTest { testContext.close(); } - private static InstanceIdentifier createBANodeIdentifier(NodeId node) { - return InstanceIdentifier.builder(Nodes.class).child(Node.class, new NodeKey(node)).toInstance(); - } - - private static org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier createBINodeIdentifier(NodeId node) { - return org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.builder().node(Nodes.QNAME) - .nodeWithKey(Node.QNAME, NODE_ID_QNAME, node.getValue()).toInstance(); + private static org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier createBINodeIdentifier(TopLevelListKey listKey) { + return org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.builder().node(Top.QNAME) + .nodeWithKey(TopLevelList.QNAME, NODE_ID_QNAME, listKey.getName()).toInstance(); } - private Future> addFlowResult(boolean success, long xid) { - AddFlowOutput output = new AddFlowOutputBuilder() // - .setTransactionId(new TransactionId(BigInteger.valueOf(xid))).build(); - RpcResult result = RpcResultBuilder.status(success).withResult(output).build(); + private Future> knockResult(boolean success, String answer) { + KnockKnockOutput output = new KnockKnockOutputBuilder() // + .setAnswer(answer).build(); + RpcResult result = RpcResultBuilder.status(success).withResult(output).build(); return Futures.immediateFuture(result); } - private static AddFlowInputBuilder addFlow(InstanceIdentifier nodeId) { - AddFlowInputBuilder builder = new AddFlowInputBuilder(); - builder.setNode(new NodeRef(nodeId)); + private static KnockKnockInputBuilder knockKnock(InstanceIdentifier listId) { + KnockKnockInputBuilder builder = new KnockKnockInputBuilder(); + builder.setKnockerId(listId); return builder; } - private CompositeNode toDomRpc(QName rpcName, AddFlowInput addFlowA) { + private CompositeNode toDomRpc(QName rpcName, KnockKnockInput knockInput) { return new CompositeNodeTOImpl(rpcName, null, - Collections.> singletonList(toDomRpcInput(addFlowA))); + Collections.>singletonList(toDomRpcInput(knockInput))); } } diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/DOMRpcServiceTestBugfix560.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/DOMRpcServiceTestBugfix560.java index 7d616ca62c..7595ec0105 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/DOMRpcServiceTestBugfix560.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/DOMRpcServiceTestBugfix560.java @@ -28,12 +28,11 @@ import org.opendaylight.controller.sal.binding.test.util.BindingTestContext; import org.opendaylight.controller.sal.core.api.RpcImplementation; import org.opendaylight.controller.sal.core.api.mount.MountProvisionInstance; import org.opendaylight.controller.sal.core.api.mount.MountProvisionService; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.bi.ba.rpcservice.rev140701.OpendaylightTestRpcServiceService; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.bi.ba.rpcservice.rev140701.RockTheHouseInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelListKey; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.util.BindingReflections; import org.opendaylight.yangtools.yang.common.QName; @@ -64,11 +63,11 @@ public class DOMRpcServiceTestBugfix560 { private final static QName RPC_NAME = QName.create(RPC_SERVICE_NAMESPACE, REVISION_DATE, "rock-the-house"); - private static final NodeId MOUNT_NODE = new NodeId("id"); - private static final QName NODE_ID_QNAME = QName.create(Node.QNAME, "id"); + private static final String TLL_NAME = "id"; + private static final QName TLL_NAME_QNAME = QName.create(TopLevelList.QNAME, "name"); - private static final InstanceIdentifier BA_MOUNT_ID = createBANodeIdentifier(MOUNT_NODE); - private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier BI_MOUNT_ID = createBINodeIdentifier(MOUNT_NODE); + private static final InstanceIdentifier BA_MOUNT_ID = createBATllIdentifier(TLL_NAME); + private static final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier BI_MOUNT_ID = createBITllIdentifier(TLL_NAME); private BindingTestContext testContext; private MountProvisionService domMountPointService; @@ -104,18 +103,18 @@ public class DOMRpcServiceTestBugfix560 { schemaContext = mountSchemaContext; } - private static org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier createBINodeIdentifier( - final NodeId mountNode) { + private static org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier createBITllIdentifier( + final String mount) { return org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier - .builder().node(Nodes.QNAME) - .nodeWithKey(Node.QNAME, NODE_ID_QNAME, mountNode.getValue()) + .builder().node(Top.QNAME) + .nodeWithKey(TopLevelList.QNAME, TLL_NAME_QNAME, mount) .toInstance(); } - private static InstanceIdentifier createBANodeIdentifier( - final NodeId mountNode) { - return InstanceIdentifier.builder(Nodes.class) - .child(Node.class, new NodeKey(mountNode)).toInstance(); + private static InstanceIdentifier createBATllIdentifier( + final String mount) { + return InstanceIdentifier.builder(Top.class) + .child(TopLevelList.class, new TopLevelListKey(mount)).toInstance(); } @SuppressWarnings("deprecation") diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/MessageCapturingFlowService.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/MessageCapturingFlowService.java index 47e79650fe..0f9389703f 100644 --- a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/MessageCapturingFlowService.java +++ b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/sal/binding/test/connect/dom/MessageCapturingFlowService.java @@ -11,15 +11,11 @@ import static org.junit.Assert.assertNotNull; import java.util.concurrent.Future; -import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry; import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcRegistration; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowOutput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutput; +import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.KnockKnockInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.KnockKnockOutput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.of.migration.test.model.rev150210.OpendaylightOfMigrationTestModelService; import org.opendaylight.yangtools.yang.binding.BaseIdentity; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; @@ -27,76 +23,28 @@ import org.opendaylight.yangtools.yang.common.RpcResult; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; -public class MessageCapturingFlowService implements SalFlowService, AutoCloseable { - - private Future> addFlowResult; - private Future> removeFlowResult; - private Future> updateFlowResult; - - private final Multimap, AddFlowInput> receivedAddFlows = HashMultimap.create(); - private final Multimap, RemoveFlowInput> receivedRemoveFlows = HashMultimap.create(); - private final Multimap, UpdateFlowInput> receivedUpdateFlows = HashMultimap.create(); - private RoutedRpcRegistration registration; - - @Override - public Future> addFlow(AddFlowInput arg0) { - receivedAddFlows.put(arg0.getNode().getValue(), arg0); - return addFlowResult; - } - - @Override - public Future> removeFlow(RemoveFlowInput arg0) { - receivedRemoveFlows.put(arg0.getNode().getValue(), arg0); - return removeFlowResult; - } - - @Override - public Future> updateFlow(UpdateFlowInput arg0) { - receivedUpdateFlows.put(arg0.getNode().getValue(), arg0); - return updateFlowResult; - } - - public Future> getAddFlowResult() { - return addFlowResult; - } +public class MessageCapturingFlowService implements OpendaylightOfMigrationTestModelService, AutoCloseable { - public MessageCapturingFlowService setAddFlowResult(Future> addFlowResult) { - this.addFlowResult = addFlowResult; - return this; - } + private Future> knockKnockResult; - public Future> getRemoveFlowResult() { - return removeFlowResult; - } - - public MessageCapturingFlowService setRemoveFlowResult(Future> removeFlowResult) { - this.removeFlowResult = removeFlowResult; - return this; - } + private final Multimap, KnockKnockInput> receivedKnocks = HashMultimap.create(); + private RoutedRpcRegistration registration; - public Future> getUpdateFlowResult() { - return updateFlowResult; + public Future> getKnockKnockResult() { + return knockKnockResult; } - public MessageCapturingFlowService setUpdateFlowResult(Future> updateFlowResult) { - this.updateFlowResult = updateFlowResult; + public MessageCapturingFlowService setKnockKnockResult(Future> kkOutput) { + this.knockKnockResult = kkOutput; return this; } - public Multimap, AddFlowInput> getReceivedAddFlows() { - return receivedAddFlows; - } - - public Multimap, RemoveFlowInput> getReceivedRemoveFlows() { - return receivedRemoveFlows; - } - - public Multimap, UpdateFlowInput> getReceivedUpdateFlows() { - return receivedUpdateFlows; + public Multimap, KnockKnockInput> getReceivedKnocks() { + return receivedKnocks; } public MessageCapturingFlowService registerTo(RpcProviderRegistry registry) { - registration = registry.addRoutedRpcImplementation(SalFlowService.class, this); + registration = registry.addRoutedRpcImplementation(OpendaylightOfMigrationTestModelService.class, this); assertNotNull(registration); return this; } @@ -125,5 +73,11 @@ public class MessageCapturingFlowService implements SalFlowService, AutoCloseabl return ret; } + @Override + public Future> knockKnock(KnockKnockInput input) { + receivedKnocks.put(input.getKnockerId(), input); + return knockKnockResult; + } + } diff --git a/opendaylight/md-sal/sal-binding-it/pom.xml b/opendaylight/md-sal/sal-binding-it/pom.xml index 3b504f45b1..7c6710fdbb 100644 --- a/opendaylight/md-sal/sal-binding-it/pom.xml +++ b/opendaylight/md-sal/sal-binding-it/pom.xml @@ -69,6 +69,10 @@ org.opendaylight.controller netconf-monitoring + + org.opendaylight.controller + netconf-notifications-api + org.opendaylight.controller sal-binding-broker-impl @@ -77,6 +81,7 @@ org.opendaylight.yangtools.thirdparty antlr4-runtime-osgi-nohead + @@ -119,9 +124,17 @@ log4j-over-slf4j - org.opendaylight.controller.model - model-flow-service - provided + org.slf4j + slf4j-api + + + org.opendaylight.controller + sal-test-model + ${mdsal.version} + + + org.opendaylight.yangtools.model + opendaylight-l2-types diff --git a/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java b/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java index 07d205bfcb..96f52bd8dc 100644 --- a/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java +++ b/opendaylight/md-sal/sal-binding-it/src/main/java/org/opendaylight/controller/test/sal/binding/it/TestHelper.java @@ -10,8 +10,8 @@ package org.opendaylight.controller.test.sal.binding.it; import static org.ops4j.pax.exam.CoreOptions.frameworkProperty; import static org.ops4j.pax.exam.CoreOptions.junitBundles; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; +import static org.ops4j.pax.exam.CoreOptions.systemPackages; import static org.ops4j.pax.exam.CoreOptions.systemProperty; - import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.options.DefaultCompositeOption; import org.ops4j.pax.exam.util.PathUtils; @@ -47,7 +47,7 @@ public class TestHelper { bindingAwareSalBundles(), mavenBundle("commons-codec", "commons-codec").versionAsInProject(), - systemProperty("org.osgi.framework.system.packages.extra").value("sun.nio.ch"), + systemPackages("sun.nio.ch", "sun.misc"), mavenBundle("io.netty", "netty-common").versionAsInProject(), // mavenBundle("io.netty", "netty-buffer").versionAsInProject(), // mavenBundle("io.netty", "netty-handler").versionAsInProject(), // @@ -83,6 +83,9 @@ public class TestHelper { mavenBundle("org.eclipse.birt.runtime.3_7_1", "org.apache.xml.resolver", "1.2.0"), mavenBundle(CONTROLLER, "config-netconf-connector").versionAsInProject(), // + mavenBundle(CONTROLLER, "netconf-notifications-api").versionAsInProject(), // + mavenBundle(CONTROLLER, "ietf-netconf").versionAsInProject(), // + mavenBundle(CONTROLLER, "ietf-netconf-notifications").versionAsInProject(), // mavenBundle(CONTROLLER, "netconf-impl").versionAsInProject(), // mavenBundle(CONTROLLER, "config-persister-file-xml-adapter").versionAsInProject().noStart(), @@ -123,7 +126,8 @@ public class TestHelper { mavenBundle(CONTROLLER, "sal-common-util").versionAsInProject(), // // - mavenBundle(CONTROLLER, "sal-inmemory-datastore").versionAsInProject(), // / + mavenBundle("com.lmax", "disruptor").versionAsInProject(), + mavenBundle(CONTROLLER, "sal-inmemory-datastore").versionAsInProject(), // mavenBundle(CONTROLLER, "sal-broker-impl").versionAsInProject(), // // mavenBundle(CONTROLLER, "sal-core-spi").versionAsInProject().update(), // @@ -159,13 +163,23 @@ public class TestHelper { } + /** + * @return option containing models for testing purposes + */ + public static Option salTestModelBundles() { + return new DefaultCompositeOption( // + mavenBundle(CONTROLLER, "sal-test-model").versionAsInProject() + ); + + } + public static Option baseModelBundles() { return new DefaultCompositeOption( // mavenBundle(YANGTOOLS_MODELS, "yang-ext").versionAsInProject(), // // mavenBundle(YANGTOOLS_MODELS, "ietf-inet-types").versionAsInProject(), // // mavenBundle(YANGTOOLS_MODELS, "ietf-yang-types").versionAsInProject(), // // - mavenBundle(YANGTOOLS_MODELS, "opendaylight-l2-types").versionAsInProject(), // // - mavenBundle(CONTROLLER_MODELS, "model-inventory").versionAsInProject()); + mavenBundle(YANGTOOLS_MODELS, "opendaylight-l2-types").versionAsInProject() // // + ); } public static Option junitAndMockitoBundles() { diff --git a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java index b2f89cf779..2075ba4421 100644 --- a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java +++ b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/AbstractTest.java @@ -20,7 +20,7 @@ import javax.inject.Inject; import static org.opendaylight.controller.test.sal.binding.it.TestHelper.baseModelBundles; import static org.opendaylight.controller.test.sal.binding.it.TestHelper.bindingAwareSalBundles; import static org.opendaylight.controller.test.sal.binding.it.TestHelper.configMinumumBundles; -import static org.opendaylight.controller.test.sal.binding.it.TestHelper.flowCapableModelBundles; +import static org.opendaylight.controller.test.sal.binding.it.TestHelper.salTestModelBundles; import static org.opendaylight.controller.test.sal.binding.it.TestHelper.junitAndMockitoBundles; import static org.opendaylight.controller.test.sal.binding.it.TestHelper.mdSalCoreBundles; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; @@ -82,7 +82,7 @@ public abstract class AbstractTest { configMinumumBundles(), // BASE Models baseModelBundles(), - flowCapableModelBundles(), + salTestModelBundles(), // Set fail if unresolved bundle present systemProperty("pax.exam.osgi.unresolved.fail").value("true"), diff --git a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/DataServiceTest.java b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/DataServiceTest.java index 33039ea231..853ff4c3f6 100644 --- a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/DataServiceTest.java +++ b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/DataServiceTest.java @@ -11,10 +11,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import com.google.inject.Inject; import java.util.concurrent.Future; -import org.junit.Before; -import org.junit.Ignore; + import org.junit.Test; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext; @@ -22,36 +20,36 @@ import org.opendaylight.controller.sal.binding.api.BindingAwareConsumer; import org.opendaylight.controller.sal.binding.api.data.DataBrokerService; import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction; import org.opendaylight.controller.sal.core.api.Broker; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.Lists; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.lists.UnorderedContainer; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.lists.unordered.container.UnorderedList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.lists.unordered.container.UnorderedListBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.lists.unordered.container.UnorderedListKey; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; +import com.google.inject.Inject; + +/** + * covers creating, reading and deleting of an item in dataStore + */ public class DataServiceTest extends AbstractTest { protected DataBrokerService consumerDataService; - @Inject Broker broker2; - @Before - public void setUp() throws Exception { - } - - /* + /** * * Ignored this, because classes here are constructed from * very different class loader as MD-SAL is run into, * this is code is run from different classloader. * + * @throws Exception */ @Test - @Ignore public void test() throws Exception { BindingAwareConsumer consumer1 = new BindingAwareConsumer() { @@ -60,7 +58,7 @@ public class DataServiceTest extends AbstractTest { consumerDataService = session.getSALService(DataBrokerService.class); } }; - broker.registerConsumer(consumer1, getBundleContext()); + broker.registerConsumer(consumer1); assertNotNull(consumerDataService); @@ -68,10 +66,10 @@ public class DataServiceTest extends AbstractTest { DataModificationTransaction transaction = consumerDataService.beginTransaction(); assertNotNull(transaction); - InstanceIdentifier node1 = createNodeRef("0"); - DataObject node = consumerDataService.readConfigurationData(node1); + InstanceIdentifier node1 = createNodeRef("0"); + DataObject node = consumerDataService.readConfigurationData(node1); assertNull(node); - Node nodeData1 = createNode("0"); + UnorderedList nodeData1 = createNode("0"); transaction.putConfigurationData(node1, nodeData1); Future> commitResult = transaction.commit(); @@ -83,13 +81,13 @@ public class DataServiceTest extends AbstractTest { assertNotNull(result.getResult()); assertEquals(TransactionStatus.COMMITED, result.getResult()); - Node readedData = (Node) consumerDataService.readConfigurationData(node1); + UnorderedList readedData = (UnorderedList) consumerDataService.readConfigurationData(node1); assertNotNull(readedData); assertEquals(nodeData1.getKey(), readedData.getKey()); DataModificationTransaction transaction2 = consumerDataService.beginTransaction(); - assertNotNull(transaction); + assertNotNull(transaction2); transaction2.removeConfigurationData(node1); @@ -104,21 +102,20 @@ public class DataServiceTest extends AbstractTest { DataObject readedData2 = consumerDataService.readConfigurationData(node1); assertNull(readedData2); - - } - private static InstanceIdentifier createNodeRef(final String string) { - NodeKey key = new NodeKey(new NodeId(string)); - return InstanceIdentifier.builder(Nodes.class).child(Node.class, key).build(); + private static InstanceIdentifier createNodeRef(final String string) { + UnorderedListKey key = new UnorderedListKey(string); + return InstanceIdentifier.builder(Lists.class).child(UnorderedContainer.class).child(UnorderedList.class, key).build(); } - private static Node createNode(final String string) { - NodeBuilder ret = new NodeBuilder(); - NodeId id = new NodeId(string); - ret.setKey(new NodeKey(id)); - ret.setId(id); + private static UnorderedList createNode(final String string) { + UnorderedListBuilder ret = new UnorderedListBuilder(); + UnorderedListKey nodeKey = new UnorderedListKey(string); + ret.setKey(nodeKey); + ret.setName("name of " + string); + ret.setName("value of " + string); return ret.build(); } } diff --git a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/NotificationTest.java b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/NotificationTest.java index 8f8e475efe..e1d5d0060d 100644 --- a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/NotificationTest.java +++ b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/NotificationTest.java @@ -10,12 +10,9 @@ package org.opendaylight.controller.test.sal.binding.it; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext; import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext; @@ -23,40 +20,37 @@ import org.opendaylight.controller.sal.binding.api.BindingAwareConsumer; import org.opendaylight.controller.sal.binding.api.BindingAwareProvider; import org.opendaylight.controller.sal.binding.api.NotificationProviderService; import org.opendaylight.controller.sal.binding.api.NotificationService; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowAdded; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowAddedBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowRemoved; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.FlowUpdated; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.NodeErrorNotification; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.NodeExperimenterErrorNotification; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowListener; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SwitchFlowRemoved; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.bi.ba.notification.rev150205.OpendaylightTestNotificationListener; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.bi.ba.notification.rev150205.OutOfPixieDustNotification; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.bi.ba.notification.rev150205.OutOfPixieDustNotificationBuilder; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.binding.NotificationListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -@Ignore +/** + * covers registering of notification listener, publishing of notification and receiving of notification. + */ public class NotificationTest extends AbstractTest { - private final FlowListener listener1 = new FlowListener(); - private final FlowListener listener2 = new FlowListener(); + private static final Logger LOG = LoggerFactory + .getLogger(NotificationTest.class); - private ListenerRegistration listener1Reg; - private ListenerRegistration listener2Reg; + protected final NotificationTestListener listener1 = new NotificationTestListener(); + protected final NotificationTestListener listener2 = new NotificationTestListener(); - private NotificationProviderService notifyProviderService; + protected ListenerRegistration listener1Reg; + protected ListenerRegistration listener2Reg; - @Before - public void setUp() throws Exception { - } + protected NotificationProviderService notifyProviderService; + /** + * test of delivering of notification + * @throws Exception + */ @Test public void notificationTest() throws Exception { - /** - * - * The registration of the Provider 1. - * - */ + LOG.info("The registration of the Provider 1."); AbstractTestProvider provider1 = new AbstractTestProvider() { @Override public void onSessionInitiated(ProviderContext session) { @@ -65,15 +59,11 @@ public class NotificationTest extends AbstractTest { }; // registerProvider method calls onSessionInitiated method above - broker.registerProvider(provider1, getBundleContext()); + broker.registerProvider(provider1); assertNotNull(notifyProviderService); - /** - * - * The registration of the Consumer 1. It retrieves Notification Service - * from MD-SAL and registers SalFlowListener as notification listener - * - */ + LOG.info("The registration of the Consumer 1. It retrieves Notification Service " + + "from MD-SAL and registers OpendaylightTestNotificationListener as notification listener"); BindingAwareConsumer consumer1 = new BindingAwareConsumer() { @Override public void onSessionInitialized(ConsumerContext session) { @@ -83,29 +73,26 @@ public class NotificationTest extends AbstractTest { } }; // registerConsumer method calls onSessionInitialized method above - broker.registerConsumer(consumer1, getBundleContext()); + broker.registerConsumer(consumer1); assertNotNull(listener1Reg); - /** - * The notification of type FlowAdded with cookie ID 0 is created. The - * delay 100ms to make sure that the notification was delivered to - * listener. - */ - notifyProviderService.publish(flowAdded(0)); + LOG.info("The notification of type FlowAdded with cookie ID 0 is created. The " + + "delay 100ms to make sure that the notification was delivered to " + + "listener."); + notifyProviderService.publish(noDustNotification("rainy day", 42)); Thread.sleep(100); /** * Check that one notification was delivered and has correct cookie. * */ - assertEquals(1, listener1.addedFlows.size()); - assertEquals(0, listener1.addedFlows.get(0).getCookie().getValue().intValue()); + assertEquals(1, listener1.notificationBag.size()); + assertEquals("rainy day", listener1.notificationBag.get(0).getReason()); + assertEquals(42, listener1.notificationBag.get(0).getDaysTillNewDust().intValue()); - /** - * The registration of the Consumer 2. SalFlowListener is registered - * registered as notification listener. - */ + LOG.info("The registration of the Consumer 2. SalFlowListener is registered " + + "registered as notification listener."); BindingAwareProvider provider = new BindingAwareProvider() { @Override @@ -116,14 +103,12 @@ public class NotificationTest extends AbstractTest { }; // registerConsumer method calls onSessionInitialized method above - broker.registerProvider(provider, getBundleContext()); + broker.registerProvider(provider); - /** - * 3 notifications are published - */ - notifyProviderService.publish(flowAdded(5)); - notifyProviderService.publish(flowAdded(10)); - notifyProviderService.publish(flowAdded(2)); + LOG.info("3 notifications are published"); + notifyProviderService.publish(noDustNotification("rainy day", 5)); + notifyProviderService.publish(noDustNotification("rainy day", 10)); + notifyProviderService.publish(noDustNotification("tax collector", 2)); /** * The delay 100ms to make sure that the notifications were delivered to @@ -136,8 +121,8 @@ public class NotificationTest extends AbstractTest { * received 4 in total, second 3 in total). * */ - assertEquals(4, listener1.addedFlows.size()); - assertEquals(3, listener2.addedFlows.size()); + assertEquals(4, listener1.notificationBag.size()); + assertEquals(3, listener2.notificationBag.size()); /** * The second listener is closed (unregistered) @@ -145,11 +130,8 @@ public class NotificationTest extends AbstractTest { */ listener2Reg.close(); - /** - * - * The notification 5 is published - */ - notifyProviderService.publish(flowAdded(10)); + LOG.info("The notification 5 is published"); + notifyProviderService.publish(noDustNotification("entomologist hunt", 10)); /** * The delay 100ms to make sure that the notification was delivered to @@ -163,73 +145,38 @@ public class NotificationTest extends AbstractTest { * second consumer because its listener was unregistered. * */ - assertEquals(5, listener1.addedFlows.size()); - assertEquals(3, listener2.addedFlows.size()); + assertEquals(5, listener1.notificationBag.size()); + assertEquals(3, listener2.notificationBag.size()); } /** - * Creates instance of the type FlowAdded. Only cookie value is set. It is + * Creates instance of the type OutOfPixieDustNotification. It is * used only for testing purpose. * - * @param i - * cookie value - * @return instance of the type FlowAdded + * @param reason + * @param days + * @return instance of the type OutOfPixieDustNotification */ - public static FlowAdded flowAdded(int i) { - FlowAddedBuilder ret = new FlowAddedBuilder(); - ret.setCookie(new FlowCookie(BigInteger.valueOf(i))); + public static OutOfPixieDustNotification noDustNotification(String reason, int days) { + OutOfPixieDustNotificationBuilder ret = new OutOfPixieDustNotificationBuilder(); + ret.setReason(reason).setDaysTillNewDust(days); return ret.build(); } /** * * Implements - * {@link org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowListener - * SalFlowListener} and contains attributes which keep lists of objects of - * the type - * {@link org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819. NodeFlow - * NodeFlow}. The lists are defined for flows which were added, removed or - * updated. + * {@link OpendaylightTestNotificationListener} and contains attributes which keep lists of objects of + * the type {@link OutOfFairyDustNotification}. */ - private static class FlowListener implements SalFlowListener { - - List addedFlows = new ArrayList<>(); - List removedFlows = new ArrayList<>(); - List updatedFlows = new ArrayList<>(); - - @Override - public void onFlowAdded(FlowAdded notification) { - addedFlows.add(notification); - } - - @Override - public void onFlowRemoved(FlowRemoved notification) { - removedFlows.add(notification); - }; - - @Override - public void onFlowUpdated(FlowUpdated notification) { - updatedFlows.add(notification); - } - - @Override - public void onSwitchFlowRemoved(SwitchFlowRemoved notification) { - // TODO Auto-generated method stub - - } + public static class NotificationTestListener implements OpendaylightTestNotificationListener { - @Override - public void onNodeErrorNotification(NodeErrorNotification notification) { - // TODO Auto-generated method stub - - } + List notificationBag = new ArrayList<>(); @Override - public void onNodeExperimenterErrorNotification( - NodeExperimenterErrorNotification notification) { - // TODO Auto-generated method stub - + public void onOutOfPixieDustNotification(OutOfPixieDustNotification arg0) { + notificationBag.add(arg0); } } diff --git a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java index d49d6f0e25..724403876e 100644 --- a/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java +++ b/opendaylight/md-sal/sal-binding-it/src/test/java/org/opendaylight/controller/test/sal/binding/it/RoutedServiceTest.java @@ -14,8 +14,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import java.math.BigInteger; - import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext; @@ -23,32 +21,41 @@ import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderCo import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcRegistration; import org.opendaylight.controller.sal.binding.api.BindingAwareConsumer; import org.opendaylight.controller.sal.binding.api.BindingAwareProvider; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInput; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInputBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService; -import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeContext; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; -import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.rpc.routing.rev140701.OpendaylightTestRoutedRpcService; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.rpc.routing.rev140701.RoutedSimpleRouteInput; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.rpc.routing.rev140701.RoutedSimpleRouteInputBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.rpc.routing.rev140701.TestContext; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.Lists; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.lists.UnorderedContainer; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.lists.unordered.container.UnorderedList; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.store.rev140422.lists.unordered.container.UnorderedListKey; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +/** + * covers routed rpc creation, registration, invocation, unregistration + */ public class RoutedServiceTest extends AbstractTest { - private SalFlowService salFlowService1; - private SalFlowService salFlowService2; + private static final Logger LOG = LoggerFactory + .getLogger(RoutedServiceTest.class); - private SalFlowService consumerService; + protected OpendaylightTestRoutedRpcService odlRoutedService1; + protected OpendaylightTestRoutedRpcService odlRoutedService2; - private RoutedRpcRegistration firstReg; - private RoutedRpcRegistration secondReg; + protected OpendaylightTestRoutedRpcService consumerService; + protected RoutedRpcRegistration firstReg; + protected RoutedRpcRegistration secondReg; + + /** + * prepare mocks + */ @Before - public void setUp() throws Exception { - salFlowService1 = mock(SalFlowService.class, "First Flow Service"); - salFlowService2 = mock(SalFlowService.class, "Second Flow Service"); + public void setUp() { + odlRoutedService1 = mock(OpendaylightTestRoutedRpcService.class, "First Flow Service"); + odlRoutedService2 = mock(OpendaylightTestRoutedRpcService.class, "Second Flow Service"); } @Test @@ -57,130 +64,106 @@ public class RoutedServiceTest extends AbstractTest { assertNotNull(getBroker()); BindingAwareProvider provider1 = new AbstractTestProvider() { - @Override public void onSessionInitiated(ProviderContext session) { assertNotNull(session); - firstReg = session.addRoutedRpcImplementation(SalFlowService.class, salFlowService1); + firstReg = session.addRoutedRpcImplementation(OpendaylightTestRoutedRpcService.class, odlRoutedService1); } }; - /** - * Register provider 1 with first implementation of SalFlowService - - * service1 - * - */ - broker.registerProvider(provider1, getBundleContext()); + LOG.info("Register provider 1 with first implementation of routeSimpleService - service1"); + broker.registerProvider(provider1); assertNotNull("Registration should not be null", firstReg); - assertSame(salFlowService1, firstReg.getInstance()); + assertSame(odlRoutedService1, firstReg.getInstance()); BindingAwareProvider provider2 = new AbstractTestProvider() { - @Override public void onSessionInitiated(ProviderContext session) { assertNotNull(session); - secondReg = session.addRoutedRpcImplementation(SalFlowService.class, salFlowService2); + secondReg = session.addRoutedRpcImplementation(OpendaylightTestRoutedRpcService.class, odlRoutedService2); } }; - /** - * Register provider 2 with first implementation of SalFlowService - - * service2 - * - */ - broker.registerProvider(provider2, getBundleContext()); + LOG.info("Register provider 2 with second implementation of routeSimpleService - service2"); + broker.registerProvider(provider2); assertNotNull("Registration should not be null", firstReg); - assertSame(salFlowService2, secondReg.getInstance()); + assertSame(odlRoutedService2, secondReg.getInstance()); assertNotSame(secondReg, firstReg); BindingAwareConsumer consumer = new BindingAwareConsumer() { @Override public void onSessionInitialized(ConsumerContext session) { - consumerService = session.getRpcService(SalFlowService.class); + consumerService = session.getRpcService(OpendaylightTestRoutedRpcService.class); } }; - broker.registerConsumer(consumer, getBundleContext()); + LOG.info("Register routeService consumer"); + broker.registerConsumer(consumer); - assertNotNull("MD-SAL instance of Flow Service should be returned", consumerService); - assertNotSame("Provider instance and consumer instance should not be same.", salFlowService1, consumerService); + assertNotNull("MD-SAL instance of test Service should be returned", consumerService); + assertNotSame("Provider instance and consumer instance should not be same.", odlRoutedService1, consumerService); - NodeRef nodeOne = createNodeRef("foo:node:1"); + InstanceIdentifier nodeOnePath = createNodeRef("foo:node:1"); - /** - * Provider 1 registers path of node 1 - */ - firstReg.registerPath(NodeContext.class, nodeOne.getValue()); + LOG.info("Provider 1 registers path of node 1"); + firstReg.registerPath(TestContext.class, nodeOnePath); /** * Consumer creates addFlow message for node one and sends it to the * MD-SAL - * */ - AddFlowInput addFlowFirstMessage = createSampleAddFlow(nodeOne, 1); - consumerService.addFlow(addFlowFirstMessage); + RoutedSimpleRouteInput simpleRouteFirstFoo = createSimpleRouteInput(nodeOnePath); + consumerService.routedSimpleRoute(simpleRouteFirstFoo); /** * Verifies that implementation of the first provider received the same * message from MD-SAL. - * */ - verify(salFlowService1).addFlow(addFlowFirstMessage); - + verify(odlRoutedService1).routedSimpleRoute(simpleRouteFirstFoo); /** * Verifies that second instance was not invoked with first message - * */ - verify(salFlowService2, times(0)).addFlow(addFlowFirstMessage); + verify(odlRoutedService2, times(0)).routedSimpleRoute(simpleRouteFirstFoo); - /** - * Provider 2 registers path of node 2 - * - */ - NodeRef nodeTwo = createNodeRef("foo:node:2"); - secondReg.registerPath(NodeContext.class, nodeTwo.getValue()); + LOG.info("Provider 2 registers path of node 2"); + InstanceIdentifier nodeTwo = createNodeRef("foo:node:2"); + secondReg.registerPath(TestContext.class, nodeTwo); /** * Consumer sends message to nodeTwo for three times. Should be * processed by second instance. */ - AddFlowInput AddFlowSecondMessage = createSampleAddFlow(nodeTwo, 2); - consumerService.addFlow(AddFlowSecondMessage); - consumerService.addFlow(AddFlowSecondMessage); - consumerService.addFlow(AddFlowSecondMessage); + RoutedSimpleRouteInput simpleRouteSecondFoo = createSimpleRouteInput(nodeTwo); + consumerService.routedSimpleRoute(simpleRouteSecondFoo); + consumerService.routedSimpleRoute(simpleRouteSecondFoo); + consumerService.routedSimpleRoute(simpleRouteSecondFoo); /** * Verifies that second instance was invoked 3 times with second message * and first instance wasn't invoked. * */ - verify(salFlowService2, times(3)).addFlow(AddFlowSecondMessage); - verify(salFlowService1, times(0)).addFlow(AddFlowSecondMessage); + verify(odlRoutedService2, times(3)).routedSimpleRoute(simpleRouteSecondFoo); + verify(odlRoutedService1, times(0)).routedSimpleRoute(simpleRouteSecondFoo); - /** - * Unregisteration of the path for the node one in the first provider - * - */ - firstReg.unregisterPath(NodeContext.class, nodeOne.getValue()); + LOG.info("Unregistration of the path for the node one in the first provider"); + firstReg.unregisterPath(TestContext.class, nodeOnePath); - /** - * Provider 2 registers path of node 1 - * - */ - secondReg.registerPath(NodeContext.class, nodeOne.getValue()); + LOG.info("Provider 2 registers path of node 1"); + secondReg.registerPath(TestContext.class, nodeOnePath); /** * A consumer sends third message to node 1 - * */ - AddFlowInput AddFlowThirdMessage = createSampleAddFlow(nodeOne, 3); - consumerService.addFlow(AddFlowThirdMessage); + RoutedSimpleRouteInput simpleRouteThirdFoo = createSimpleRouteInput(nodeOnePath); + consumerService.routedSimpleRoute(simpleRouteThirdFoo); /** * Verifies that provider 1 wasn't invoked and provider 2 was invoked 1 * time. + * TODO: fix unregister path */ - verify(salFlowService1, times(0)).addFlow(AddFlowThirdMessage); - verify(salFlowService2).addFlow(AddFlowThirdMessage); + //verify(odlRoutedService1, times(0)).routedSimpleRoute(simpleRouteThirdFoo); + verify(odlRoutedService2).routedSimpleRoute(simpleRouteThirdFoo); } @@ -189,13 +172,16 @@ public class RoutedServiceTest extends AbstractTest { * * @param string * string with key(path) - * @return instance of the type NodeRef + * @return instance identifier to {@link UnorderedList} */ - private static NodeRef createNodeRef(String string) { - NodeKey key = new NodeKey(new NodeId(string)); - InstanceIdentifier path = InstanceIdentifier.builder(Nodes.class).child(Node.class, key).build(); - - return new NodeRef(path); + private static InstanceIdentifier createNodeRef(String string) { + UnorderedListKey key = new UnorderedListKey(string); + InstanceIdentifier path = InstanceIdentifier.builder(Lists.class) + .child(UnorderedContainer.class) + .child(UnorderedList.class, key) + .build(); + + return path; } /** @@ -203,14 +189,11 @@ public class RoutedServiceTest extends AbstractTest { * * @param node * NodeRef value - * @param cookie - * integer with cookie value - * @return AddFlowInput instance + * @return simpleRouteInput instance */ - static AddFlowInput createSampleAddFlow(NodeRef node, int cookie) { - AddFlowInputBuilder ret = new AddFlowInputBuilder(); - ret.setNode(node); - ret.setCookie(new FlowCookie(BigInteger.valueOf(cookie))); + static RoutedSimpleRouteInput createSimpleRouteInput(InstanceIdentifier node) { + RoutedSimpleRouteInputBuilder ret = new RoutedSimpleRouteInputBuilder(); + ret.setRoute(node); return ret.build(); } } diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractUntypedActor.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractUntypedActor.java index cf37cbdd00..a604b05c01 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractUntypedActor.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractUntypedActor.java @@ -9,12 +9,11 @@ package org.opendaylight.controller.cluster.common.actor; import akka.actor.UntypedActor; -import akka.event.Logging; -import akka.event.LoggingAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class AbstractUntypedActor extends UntypedActor { - protected final LoggingAdapter LOG = - Logging.getLogger(getContext().system(), this); + protected final Logger LOG = LoggerFactory.getLogger(getClass()); public AbstractUntypedActor() { if(LOG.isDebugEnabled()) { @@ -30,11 +29,11 @@ public abstract class AbstractUntypedActor extends UntypedActor { @Override public void onReceive(Object message) throws Exception { final String messageType = message.getClass().getSimpleName(); if(LOG.isDebugEnabled()) { - LOG.debug("Received message {}", messageType); +// LOG.debug("Received message {}", messageType); } handleReceive(message); if(LOG.isDebugEnabled()) { - LOG.debug("Done handling message {}", messageType); +// LOG.debug("Done handling message {}", messageType); } } diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractUntypedPersistentActor.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractUntypedPersistentActor.java index 8a6217deab..432c2d5615 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractUntypedPersistentActor.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/common/actor/AbstractUntypedPersistentActor.java @@ -8,21 +8,20 @@ package org.opendaylight.controller.cluster.common.actor; -import akka.event.Logging; -import akka.event.LoggingAdapter; import akka.japi.Procedure; import akka.persistence.SnapshotSelectionCriteria; import akka.persistence.UntypedPersistentActor; import org.opendaylight.controller.cluster.DataPersistenceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class AbstractUntypedPersistentActor extends UntypedPersistentActor { - protected final LoggingAdapter LOG = - Logging.getLogger(getContext().system(), this); + protected final Logger LOG = LoggerFactory.getLogger(getClass()); public AbstractUntypedPersistentActor() { - if(LOG.isDebugEnabled()) { - LOG.debug("Actor created {}", getSelf()); + if(LOG.isTraceEnabled()) { + LOG.trace("Actor created {}", getSelf()); } getContext(). system(). @@ -34,24 +33,24 @@ public abstract class AbstractUntypedPersistentActor extends UntypedPersistentAc @Override public void onReceiveCommand(Object message) throws Exception { final String messageType = message.getClass().getSimpleName(); - if(LOG.isDebugEnabled()) { - LOG.debug("Received message {}", messageType); + if(LOG.isTraceEnabled()) { + LOG.trace("Received message {}", messageType); } handleCommand(message); - if(LOG.isDebugEnabled()) { - LOG.debug("Done handling message {}", messageType); + if(LOG.isTraceEnabled()) { + LOG.trace("Done handling message {}", messageType); } } @Override public void onReceiveRecover(Object message) throws Exception { final String messageType = message.getClass().getSimpleName(); - if(LOG.isDebugEnabled()) { - LOG.debug("Received message {}", messageType); + if(LOG.isTraceEnabled()) { + LOG.trace("Received message {}", messageType); } handleRecover(message); - if(LOG.isDebugEnabled()) { - LOG.debug("Done handling message {}", messageType); + if(LOG.isTraceEnabled()) { + LOG.trace("Done handling message {}", messageType); } } @@ -119,7 +118,7 @@ public abstract class AbstractUntypedPersistentActor extends UntypedPersistentAc try { procedure.apply(o); } catch (Exception e) { - LOG.error(e, "An unexpected error occurred"); + LOG.error("An unexpected error occurred", e); } } diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/InvalidNormalizedNodeStreamException.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/InvalidNormalizedNodeStreamException.java new file mode 100644 index 0000000000..da60496a22 --- /dev/null +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/InvalidNormalizedNodeStreamException.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.datastore.node.utils.stream; + +import java.io.IOException; + +/** + * Exception thrown from NormalizedNodeInputStreamReader when the input stream does not contain + * valid serialized data. + * + * @author Thomas Pantelis + */ +public class InvalidNormalizedNodeStreamException extends IOException { + private static final long serialVersionUID = 1L; + + public InvalidNormalizedNodeStreamException(String message) { + super(message); + } +} diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeInputStreamReader.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeInputStreamReader.java index cde338179b..bb2f5d41d9 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeInputStreamReader.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeInputStreamReader.java @@ -69,6 +69,8 @@ public class NormalizedNodeInputStreamReader implements NormalizedNodeStreamRead private final StringBuilder reusableStringBuilder = new StringBuilder(50); + private boolean readSignatureMarker = true; + public NormalizedNodeInputStreamReader(InputStream stream) throws IOException { Preconditions.checkNotNull(stream); input = new DataInputStream(stream); @@ -80,6 +82,25 @@ public class NormalizedNodeInputStreamReader implements NormalizedNodeStreamRead @Override public NormalizedNode readNormalizedNode() throws IOException { + readSignatureMarkerAndVersionIfNeeded(); + return readNormalizedNodeInternal(); + } + + private void readSignatureMarkerAndVersionIfNeeded() throws IOException { + if(readSignatureMarker) { + readSignatureMarker = false; + + byte marker = input.readByte(); + if(marker != NormalizedNodeOutputStreamWriter.SIGNATURE_MARKER) { + throw new InvalidNormalizedNodeStreamException(String.format( + "Invalid signature marker: %d", marker)); + } + + input.readShort(); // read the version - not currently used/needed. + } + } + + private NormalizedNode readNormalizedNodeInternal() throws IOException { // each node should start with a byte byte nodeType = input.readByte(); @@ -284,7 +305,7 @@ public class NormalizedNodeInputStreamReader implements NormalizedNodeStreamRead return bytes; case ValueTypes.YANG_IDENTIFIER_TYPE : - return readYangInstanceIdentifier(); + return readYangInstanceIdentifierInternal(); default : return null; @@ -292,6 +313,11 @@ public class NormalizedNodeInputStreamReader implements NormalizedNodeStreamRead } public YangInstanceIdentifier readYangInstanceIdentifier() throws IOException { + readSignatureMarkerAndVersionIfNeeded(); + return readYangInstanceIdentifierInternal(); + } + + private YangInstanceIdentifier readYangInstanceIdentifierInternal() throws IOException { int size = input.readInt(); List pathArguments = new ArrayList<>(size); @@ -342,11 +368,11 @@ public class NormalizedNodeInputStreamReader implements NormalizedNodeStreamRead lastLeafSetQName = nodeType; - LeafSetEntryNode child = (LeafSetEntryNode)readNormalizedNode(); + LeafSetEntryNode child = (LeafSetEntryNode)readNormalizedNodeInternal(); while(child != null) { builder.withChild(child); - child = (LeafSetEntryNode)readNormalizedNode(); + child = (LeafSetEntryNode)readNormalizedNodeInternal(); } return builder; } @@ -356,11 +382,11 @@ public class NormalizedNodeInputStreamReader implements NormalizedNodeStreamRead NormalizedNodeContainerBuilder builder) throws IOException { LOG.debug("Reading data container (leaf nodes) nodes"); - NormalizedNode child = readNormalizedNode(); + NormalizedNode child = readNormalizedNodeInternal(); while(child != null) { builder.addChild(child); - child = readNormalizedNode(); + child = readNormalizedNodeInternal(); } return builder; } diff --git a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeOutputStreamWriter.java b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeOutputStreamWriter.java index 088f4dfbe9..d4aab036be 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeOutputStreamWriter.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/main/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeOutputStreamWriter.java @@ -46,6 +46,9 @@ public class NormalizedNodeOutputStreamWriter implements NormalizedNodeStreamWri private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodeOutputStreamWriter.class); + static final byte SIGNATURE_MARKER = (byte) 0xab; + static final short CURRENT_VERSION = (short) 1; + static final byte IS_CODE_VALUE = 1; static final byte IS_STRING_VALUE = 2; static final byte IS_NULL_VALUE = 3; @@ -56,6 +59,8 @@ public class NormalizedNodeOutputStreamWriter implements NormalizedNodeStreamWri private NormalizedNodeWriter normalizedNodeWriter; + private boolean wroteSignatureMarker; + public NormalizedNodeOutputStreamWriter(OutputStream stream) throws IOException { Preconditions.checkNotNull(stream); output = new DataOutputStream(stream); @@ -74,9 +79,18 @@ public class NormalizedNodeOutputStreamWriter implements NormalizedNodeStreamWri } public void writeNormalizedNode(NormalizedNode node) throws IOException { + writeSignatureMarkerAndVersionIfNeeded(); normalizedNodeWriter().write(node); } + private void writeSignatureMarkerAndVersionIfNeeded() throws IOException { + if(!wroteSignatureMarker) { + output.writeByte(SIGNATURE_MARKER); + output.writeShort(CURRENT_VERSION); + wroteSignatureMarker = true; + } + } + @Override public void leafNode(YangInstanceIdentifier.NodeIdentifier name, Object value) throws IOException, IllegalArgumentException { Preconditions.checkNotNull(name, "Node identifier should not be null"); @@ -201,6 +215,9 @@ public class NormalizedNodeOutputStreamWriter implements NormalizedNodeStreamWri private void startNode(final QName qName, byte nodeType) throws IOException { Preconditions.checkNotNull(qName, "QName of node identifier should not be null."); + + writeSignatureMarkerAndVersionIfNeeded(); + // First write the type of node output.writeByte(nodeType); // Write Start Tag @@ -247,6 +264,11 @@ public class NormalizedNodeOutputStreamWriter implements NormalizedNodeStreamWri } public void writeYangInstanceIdentifier(YangInstanceIdentifier identifier) throws IOException { + writeSignatureMarkerAndVersionIfNeeded(); + writeYangInstanceIdentifierInternal(identifier); + } + + private void writeYangInstanceIdentifierInternal(YangInstanceIdentifier identifier) throws IOException { Iterable pathArguments = identifier.getPathArguments(); int size = Iterables.size(pathArguments); output.writeInt(size); @@ -363,7 +385,7 @@ public class NormalizedNodeOutputStreamWriter implements NormalizedNodeStreamWri output.write(bytes); break; case ValueTypes.YANG_IDENTIFIER_TYPE: - writeYangInstanceIdentifier((YangInstanceIdentifier) value); + writeYangInstanceIdentifierInternal((YangInstanceIdentifier) value); break; case ValueTypes.NULL_TYPE : break; diff --git a/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReaderWriterTest.java b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReaderWriterTest.java index 6528f2e4d2..67a342b440 100644 --- a/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReaderWriterTest.java +++ b/opendaylight/md-sal/sal-clustering-commons/src/test/java/org/opendaylight/controller/cluster/datastore/node/utils/stream/NormalizedNodeStreamReaderWriterTest.java @@ -15,15 +15,16 @@ import java.io.IOException; import org.apache.commons.lang.SerializationUtils; import org.junit.Assert; import org.junit.Test; +import org.opendaylight.controller.cluster.datastore.node.NormalizedNodeToNodeCodec; +import org.opendaylight.controller.cluster.datastore.util.InstanceIdentifierUtils; import org.opendaylight.controller.cluster.datastore.util.TestModel; +import org.opendaylight.controller.protobuff.messages.transaction.ShardTransactionMessages; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; -import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafSetEntryNodeBuilder; @@ -33,9 +34,13 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; public class NormalizedNodeStreamReaderWriterTest { @Test - public void testNormalizedNodeStreamReaderWriter() throws IOException { + public void testNormalizedNodeStreaming() throws IOException { - testNormalizedNodeStreamReaderWriter(createTestContainer()); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + NormalizedNodeOutputStreamWriter writer = new NormalizedNodeOutputStreamWriter(byteArrayOutputStream); + + NormalizedNode testContainer = createTestContainer(); + writer.writeNormalizedNode(testContainer); QName toaster = QName.create("http://netconfcentral.org/ns/toaster","2009-11-20","toaster"); QName darknessFactor = QName.create("http://netconfcentral.org/ns/toaster","2009-11-20","darknessFactor"); @@ -43,9 +48,21 @@ public class NormalizedNodeStreamReaderWriterTest { withNodeIdentifier(new NodeIdentifier(toaster)). withChild(ImmutableNodes.leafNode(darknessFactor, "1000")).build(); - testNormalizedNodeStreamReaderWriter(Builders.containerBuilder(). + ContainerNode toasterContainer = Builders.containerBuilder(). withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME)). - withChild(toasterNode).build()); + withChild(toasterNode).build(); + writer.writeNormalizedNode(toasterContainer); + + NormalizedNodeInputStreamReader reader = new NormalizedNodeInputStreamReader( + new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); + + NormalizedNode node = reader.readNormalizedNode(); + Assert.assertEquals(testContainer, node); + + node = reader.readNormalizedNode(); + Assert.assertEquals(toasterContainer, node); + + writer.close(); } private NormalizedNode createTestContainer() { @@ -76,24 +93,75 @@ public class NormalizedNodeStreamReaderWriterTest { build(); } - private void testNormalizedNodeStreamReaderWriter(NormalizedNode input) throws IOException { + @Test + public void testYangInstanceIdentifierStreaming() throws IOException { + YangInstanceIdentifier path = YangInstanceIdentifier.builder(TestModel.TEST_PATH). + node(TestModel.OUTER_LIST_QNAME).nodeWithKey( + TestModel.INNER_LIST_QNAME, TestModel.ID_QNAME, 10).build(); - byte[] byteData = null; + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + NormalizedNodeOutputStreamWriter writer = + new NormalizedNodeOutputStreamWriter(byteArrayOutputStream); + writer.writeYangInstanceIdentifier(path); + + NormalizedNodeInputStreamReader reader = new NormalizedNodeInputStreamReader( + new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); + + YangInstanceIdentifier newPath = reader.readYangInstanceIdentifier(); + Assert.assertEquals(path, newPath); + + writer.close(); + } + + @Test + public void testNormalizedNodeAndYangInstanceIdentifierStreaming() throws IOException { - try(ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - NormalizedNodeStreamWriter writer = new NormalizedNodeOutputStreamWriter(byteArrayOutputStream)) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + NormalizedNodeOutputStreamWriter writer = new NormalizedNodeOutputStreamWriter(byteArrayOutputStream); - NormalizedNodeWriter normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(writer); - normalizedNodeWriter.write(input); - byteData = byteArrayOutputStream.toByteArray(); + NormalizedNode testContainer = TestModel.createBaseTestContainerBuilder().build(); + writer.writeNormalizedNode(testContainer); - } + YangInstanceIdentifier path = YangInstanceIdentifier.builder(TestModel.TEST_PATH). + node(TestModel.OUTER_LIST_QNAME).nodeWithKey( + TestModel.INNER_LIST_QNAME, TestModel.ID_QNAME, 10).build(); + + writer.writeYangInstanceIdentifier(path); NormalizedNodeInputStreamReader reader = new NormalizedNodeInputStreamReader( - new ByteArrayInputStream(byteData)); + new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); NormalizedNode node = reader.readNormalizedNode(); - Assert.assertEquals(input, node); + Assert.assertEquals(testContainer, node); + + YangInstanceIdentifier newPath = reader.readYangInstanceIdentifier(); + Assert.assertEquals(path, newPath); + + writer.close(); + } + + @Test(expected=InvalidNormalizedNodeStreamException.class, timeout=10000) + public void testInvalidNormalizedNodeStream() throws IOException { + byte[] protobufBytes = new NormalizedNodeToNodeCodec(null).encode( + TestModel.createBaseTestContainerBuilder().build()).getNormalizedNode().toByteArray(); + + NormalizedNodeInputStreamReader reader = new NormalizedNodeInputStreamReader( + new ByteArrayInputStream(protobufBytes)); + + reader.readNormalizedNode(); + } + + @Test(expected=InvalidNormalizedNodeStreamException.class, timeout=10000) + public void testInvalidYangInstanceIdentifierStream() throws IOException { + YangInstanceIdentifier path = YangInstanceIdentifier.builder(TestModel.TEST_PATH).build(); + + byte[] protobufBytes = ShardTransactionMessages.DeleteData.newBuilder().setInstanceIdentifierPathArguments( + InstanceIdentifierUtils.toSerializable(path)).build().toByteArray(); + + NormalizedNodeInputStreamReader reader = new NormalizedNodeInputStreamReader( + new ByteArrayInputStream(protobufBytes)); + + reader.readYangInstanceIdentifier(); } @Test diff --git a/opendaylight/md-sal/sal-clustering-config/src/main/resources/initial/akka.conf b/opendaylight/md-sal/sal-clustering-config/src/main/resources/initial/akka.conf index f196ad1644..e72f4b2675 100644 --- a/opendaylight/md-sal/sal-clustering-config/src/main/resources/initial/akka.conf +++ b/opendaylight/md-sal/sal-clustering-config/src/main/resources/initial/akka.conf @@ -13,17 +13,27 @@ odl-cluster-data { loggers = ["akka.event.slf4j.Slf4jLogger"] actor { - provider = "akka.cluster.ClusterActorRefProvider" serializers { - java = "akka.serialization.JavaSerializer" - proto = "akka.remote.serialization.ProtobufSerializer" - } + java = "akka.serialization.JavaSerializer" + proto = "akka.remote.serialization.ProtobufSerializer" + } - serialization-bindings { - "com.google.protobuf.Message" = proto + serialization-bindings { + "com.google.protobuf.Message" = proto + } + + default-dispatcher { + # Setting throughput to 1 makes the dispatcher fair. It processes 1 message from + # the mailbox before moving on to the next mailbox + throughput = 1 + } - } + default-mailbox { + # When not using a BalancingDispatcher it is recommended that we use the SingleConsumerOnlyUnboundedMailbox + # as it is the most efficient for multiple producer/single consumer use cases + mailbox-type="akka.dispatch.SingleConsumerOnlyUnboundedMailbox" + } } remote { log-remote-lifecycle-events = off @@ -39,7 +49,7 @@ odl-cluster-data { cluster { seed-nodes = ["akka.tcp://opendaylight-cluster-data@127.0.0.1:2550"] - auto-down-unreachable-after = 10s + auto-down-unreachable-after = 300s roles = [ "member-1" @@ -71,13 +81,16 @@ odl-cluster-rpc { netty.tcp { hostname = "127.0.0.1" port = 2551 + maximum-frame-size = 419430400 + send-buffer-size = 52428800 + receive-buffer-size = 52428800 } } cluster { seed-nodes = ["akka.tcp://odl-cluster-rpc@127.0.0.1:2551"] - auto-down-unreachable-after = 10s + auto-down-unreachable-after = 300s } } } diff --git a/opendaylight/md-sal/sal-common-impl/src/main/java/org/opendaylight/controller/md/sal/common/impl/util/compat/DataNormalizer.java b/opendaylight/md-sal/sal-common-impl/src/main/java/org/opendaylight/controller/md/sal/common/impl/util/compat/DataNormalizer.java index e8b239d2c4..b4dcb1167c 100644 --- a/opendaylight/md-sal/sal-common-impl/src/main/java/org/opendaylight/controller/md/sal/common/impl/util/compat/DataNormalizer.java +++ b/opendaylight/md-sal/sal-common-impl/src/main/java/org/opendaylight/controller/md/sal/common/impl/util/compat/DataNormalizer.java @@ -14,10 +14,8 @@ import java.util.ArrayList; import java.util.Iterator; 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.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.SimpleNode; @@ -106,24 +104,6 @@ public class DataNormalizer { normalizedPath), e); } } - - // Write Augmentation data resolution - if (legacyData.getValue().size() == 1) { - final DataNormalizationOperation potentialOp; - - try { - final QName childType = legacyData.getValue().get(0).getNodeType(); - potentialOp = currentOp.getChild(childType); - } catch (DataNormalizationException e) { - throw new IllegalArgumentException(String.format("Failed to get child operation for %s", legacyData), e); - } - - if (potentialOp.getIdentifier() instanceof AugmentationIdentifier) { - currentOp = potentialOp; - normalizedPath = normalizedPath.node(potentialOp.getIdentifier()); - } - } - Preconditions.checkArgument(currentOp != null, "Instance Identifier %s does not reference correct schema Node.", normalizedPath); return new AbstractMap.SimpleEntry>(normalizedPath, diff --git a/opendaylight/md-sal/sal-common-impl/src/test/java/org/opendaylight/controller/md/sal/common/impl/util/compat/DataNormalizerTest.java b/opendaylight/md-sal/sal-common-impl/src/test/java/org/opendaylight/controller/md/sal/common/impl/util/compat/DataNormalizerTest.java index e841b866ba..ce9379a4ad 100644 --- a/opendaylight/md-sal/sal-common-impl/src/test/java/org/opendaylight/controller/md/sal/common/impl/util/compat/DataNormalizerTest.java +++ b/opendaylight/md-sal/sal-common-impl/src/test/java/org/opendaylight/controller/md/sal/common/impl/util/compat/DataNormalizerTest.java @@ -668,10 +668,6 @@ public class DataNormalizerTest { YangInstanceIdentifier.create(Lists.newArrayList(new NodeIdentifier(TEST_QNAME), new NodeIdentifier( OUTER_CONTAINER_QNAME))), outerContBuilder.toInstance())); - verifyNormalizedInstanceIdentifier(normalizedNodeEntry.getKey(), TEST_QNAME, OUTER_CONTAINER_QNAME, - Sets.newHashSet(AUGMENTED_LEAF_QNAME)); - - verifyNormalizedNode(normalizedNodeEntry.getValue(), expAugmentation); } @Test diff --git a/opendaylight/md-sal/sal-distributed-datastore/pom.xml b/opendaylight/md-sal/sal-distributed-datastore/pom.xml index d6030ea457..e27546f5dc 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/pom.xml +++ b/opendaylight/md-sal/sal-distributed-datastore/pom.xml @@ -148,6 +148,11 @@ org.opendaylight.yangtools yang-data-impl + + org.apache.commons + commons-lang3 + + diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DatastoreContext.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DatastoreContext.java index 01e42dbb8e..cee781fb88 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DatastoreContext.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DatastoreContext.java @@ -10,6 +10,7 @@ package org.opendaylight.controller.cluster.datastore; import akka.util.Timeout; import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.text.WordUtils; import org.opendaylight.controller.cluster.datastore.config.ConfigurationReader; import org.opendaylight.controller.cluster.datastore.config.FileConfigurationReader; import org.opendaylight.controller.cluster.raft.ConfigParams; @@ -25,37 +26,43 @@ import scala.concurrent.duration.FiniteDuration; */ public class DatastoreContext { - private final InMemoryDOMDataStoreConfigProperties dataStoreProperties; - private final Duration shardTransactionIdleTimeout; - private final int operationTimeoutInSeconds; - private final String dataStoreMXBeanType; - private final ConfigParams shardRaftConfig; - private final int shardTransactionCommitTimeoutInSeconds; - private final int shardTransactionCommitQueueCapacity; - private final Timeout shardInitializationTimeout; - private final Timeout shardLeaderElectionTimeout; - private final boolean persistent; - private final ConfigurationReader configurationReader; - private final long shardElectionTimeoutFactor; - - private DatastoreContext(InMemoryDOMDataStoreConfigProperties dataStoreProperties, - ConfigParams shardRaftConfig, String dataStoreMXBeanType, int operationTimeoutInSeconds, - Duration shardTransactionIdleTimeout, int shardTransactionCommitTimeoutInSeconds, - int shardTransactionCommitQueueCapacity, Timeout shardInitializationTimeout, - Timeout shardLeaderElectionTimeout, - boolean persistent, ConfigurationReader configurationReader, long shardElectionTimeoutFactor) { - this.dataStoreProperties = dataStoreProperties; - this.shardRaftConfig = shardRaftConfig; - this.dataStoreMXBeanType = dataStoreMXBeanType; - this.operationTimeoutInSeconds = operationTimeoutInSeconds; - this.shardTransactionIdleTimeout = shardTransactionIdleTimeout; - this.shardTransactionCommitTimeoutInSeconds = shardTransactionCommitTimeoutInSeconds; - this.shardTransactionCommitQueueCapacity = shardTransactionCommitQueueCapacity; - this.shardInitializationTimeout = shardInitializationTimeout; - this.shardLeaderElectionTimeout = shardLeaderElectionTimeout; - this.persistent = persistent; - this.configurationReader = configurationReader; - this.shardElectionTimeoutFactor = shardElectionTimeoutFactor; + public static final Duration DEFAULT_SHARD_TRANSACTION_IDLE_TIMEOUT = Duration.create(10, TimeUnit.MINUTES); + public static final int DEFAULT_OPERATION_TIMEOUT_IN_SECONDS = 5; + public static final int DEFAULT_SHARD_TX_COMMIT_TIMEOUT_IN_SECONDS = 30; + public static final int DEFAULT_JOURNAL_RECOVERY_BATCH_SIZE = 1000; + public static final int DEFAULT_SNAPSHOT_BATCH_COUNT = 20000; + public static final int DEFAULT_HEARTBEAT_INTERVAL_IN_MILLIS = 500; + public static final int DEFAULT_ISOLATED_LEADER_CHECK_INTERVAL_IN_MILLIS = DEFAULT_HEARTBEAT_INTERVAL_IN_MILLIS * 10; + public static final int DEFAULT_SHARD_TX_COMMIT_QUEUE_CAPACITY = 20000; + public static final Timeout DEFAULT_SHARD_INITIALIZATION_TIMEOUT = new Timeout(5, TimeUnit.MINUTES); + public static final Timeout DEFAULT_SHARD_LEADER_ELECTION_TIMEOUT = new Timeout(30, TimeUnit.SECONDS); + public static final boolean DEFAULT_PERSISTENT = true; + public static final FileConfigurationReader DEFAULT_CONFIGURATION_READER = new FileConfigurationReader(); + public static final int DEFAULT_SHARD_SNAPSHOT_DATA_THRESHOLD_PERCENTAGE = 12; + public static final int DEFAULT_SHARD_ELECTION_TIMEOUT_FACTOR = 2; + public static final int DEFAULT_TX_CREATION_INITIAL_RATE_LIMIT = 100; + public static final String UNKNOWN_DATA_STORE_TYPE = "unknown"; + + private InMemoryDOMDataStoreConfigProperties dataStoreProperties; + private Duration shardTransactionIdleTimeout = DatastoreContext.DEFAULT_SHARD_TRANSACTION_IDLE_TIMEOUT; + private int operationTimeoutInSeconds = DEFAULT_OPERATION_TIMEOUT_IN_SECONDS; + private String dataStoreMXBeanType; + private int shardTransactionCommitTimeoutInSeconds = DEFAULT_SHARD_TX_COMMIT_TIMEOUT_IN_SECONDS; + private int shardTransactionCommitQueueCapacity = DEFAULT_SHARD_TX_COMMIT_QUEUE_CAPACITY; + private Timeout shardInitializationTimeout = DEFAULT_SHARD_INITIALIZATION_TIMEOUT; + private Timeout shardLeaderElectionTimeout = DEFAULT_SHARD_LEADER_ELECTION_TIMEOUT; + private boolean persistent = DEFAULT_PERSISTENT; + private ConfigurationReader configurationReader = DEFAULT_CONFIGURATION_READER; + private long transactionCreationInitialRateLimit = DEFAULT_TX_CREATION_INITIAL_RATE_LIMIT; + private DefaultConfigParamsImpl raftConfig = new DefaultConfigParamsImpl(); + private String dataStoreType = UNKNOWN_DATA_STORE_TYPE; + + private DatastoreContext(){ + setShardJournalRecoveryLogBatchSize(DEFAULT_JOURNAL_RECOVERY_BATCH_SIZE); + setSnapshotBatchCount(DEFAULT_SNAPSHOT_BATCH_COUNT); + setHeartbeatInterval(DEFAULT_HEARTBEAT_INTERVAL_IN_MILLIS); + setIsolatedLeaderCheckInterval(DEFAULT_ISOLATED_LEADER_CHECK_INTERVAL_IN_MILLIS); + setSnapshotDataThresholdPercentage(DEFAULT_SHARD_SNAPSHOT_DATA_THRESHOLD_PERCENTAGE); } public static Builder newBuilder() { @@ -79,7 +86,7 @@ public class DatastoreContext { } public ConfigParams getShardRaftConfig() { - return shardRaftConfig; + return raftConfig; } public int getShardTransactionCommitTimeoutInSeconds() { @@ -107,125 +114,140 @@ public class DatastoreContext { } public long getShardElectionTimeoutFactor(){ - return this.shardElectionTimeoutFactor; + return raftConfig.getElectionTimeoutFactor(); + } + + public String getDataStoreType(){ + return dataStoreType; + } + + public long getTransactionCreationInitialRateLimit() { + return transactionCreationInitialRateLimit; + } + + private void setHeartbeatInterval(long shardHeartbeatIntervalInMillis){ + raftConfig.setHeartBeatInterval(new FiniteDuration(shardHeartbeatIntervalInMillis, + TimeUnit.MILLISECONDS)); + } + + private void setShardJournalRecoveryLogBatchSize(int shardJournalRecoveryLogBatchSize){ + raftConfig.setJournalRecoveryLogBatchSize(shardJournalRecoveryLogBatchSize); + } + + + private void setIsolatedLeaderCheckInterval(long shardIsolatedLeaderCheckIntervalInMillis) { + raftConfig.setIsolatedLeaderCheckInterval( + new FiniteDuration(shardIsolatedLeaderCheckIntervalInMillis, TimeUnit.MILLISECONDS)); + } + + private void setElectionTimeoutFactor(long shardElectionTimeoutFactor) { + raftConfig.setElectionTimeoutFactor(shardElectionTimeoutFactor); + } + + private void setSnapshotDataThresholdPercentage(int shardSnapshotDataThresholdPercentage) { + raftConfig.setSnapshotDataThresholdPercentage(shardSnapshotDataThresholdPercentage); + } + + private void setSnapshotBatchCount(int shardSnapshotBatchCount) { + raftConfig.setSnapshotBatchCount(shardSnapshotBatchCount); } public static class Builder { - private InMemoryDOMDataStoreConfigProperties dataStoreProperties; - private Duration shardTransactionIdleTimeout = Duration.create(10, TimeUnit.MINUTES); - private int operationTimeoutInSeconds = 5; - private String dataStoreMXBeanType; - private int shardTransactionCommitTimeoutInSeconds = 30; - private int shardJournalRecoveryLogBatchSize = 1000; - private int shardSnapshotBatchCount = 20000; - private int shardHeartbeatIntervalInMillis = 500; - private int shardTransactionCommitQueueCapacity = 20000; - private Timeout shardInitializationTimeout = new Timeout(5, TimeUnit.MINUTES); - private Timeout shardLeaderElectionTimeout = new Timeout(30, TimeUnit.SECONDS); - private boolean persistent = true; - private ConfigurationReader configurationReader = new FileConfigurationReader(); - private int shardIsolatedLeaderCheckIntervalInMillis = shardHeartbeatIntervalInMillis * 10; - private int shardSnapshotDataThresholdPercentage = 12; - private long shardElectionTimeoutFactor = 2; + private DatastoreContext datastoreContext = new DatastoreContext(); public Builder shardTransactionIdleTimeout(Duration shardTransactionIdleTimeout) { - this.shardTransactionIdleTimeout = shardTransactionIdleTimeout; + datastoreContext.shardTransactionIdleTimeout = shardTransactionIdleTimeout; return this; } public Builder operationTimeoutInSeconds(int operationTimeoutInSeconds) { - this.operationTimeoutInSeconds = operationTimeoutInSeconds; + datastoreContext.operationTimeoutInSeconds = operationTimeoutInSeconds; return this; } public Builder dataStoreMXBeanType(String dataStoreMXBeanType) { - this.dataStoreMXBeanType = dataStoreMXBeanType; + datastoreContext.dataStoreMXBeanType = dataStoreMXBeanType; return this; } public Builder dataStoreProperties(InMemoryDOMDataStoreConfigProperties dataStoreProperties) { - this.dataStoreProperties = dataStoreProperties; + datastoreContext.dataStoreProperties = dataStoreProperties; return this; } public Builder shardTransactionCommitTimeoutInSeconds(int shardTransactionCommitTimeoutInSeconds) { - this.shardTransactionCommitTimeoutInSeconds = shardTransactionCommitTimeoutInSeconds; + datastoreContext.shardTransactionCommitTimeoutInSeconds = shardTransactionCommitTimeoutInSeconds; return this; } public Builder shardJournalRecoveryLogBatchSize(int shardJournalRecoveryLogBatchSize) { - this.shardJournalRecoveryLogBatchSize = shardJournalRecoveryLogBatchSize; + datastoreContext.setShardJournalRecoveryLogBatchSize(shardJournalRecoveryLogBatchSize); return this; } public Builder shardSnapshotBatchCount(int shardSnapshotBatchCount) { - this.shardSnapshotBatchCount = shardSnapshotBatchCount; + datastoreContext.setSnapshotBatchCount(shardSnapshotBatchCount); return this; } public Builder shardSnapshotDataThresholdPercentage(int shardSnapshotDataThresholdPercentage) { - this.shardSnapshotDataThresholdPercentage = shardSnapshotDataThresholdPercentage; + datastoreContext.setSnapshotDataThresholdPercentage(shardSnapshotDataThresholdPercentage); return this; } - public Builder shardHeartbeatIntervalInMillis(int shardHeartbeatIntervalInMillis) { - this.shardHeartbeatIntervalInMillis = shardHeartbeatIntervalInMillis; + datastoreContext.setHeartbeatInterval(shardHeartbeatIntervalInMillis); return this; } public Builder shardTransactionCommitQueueCapacity(int shardTransactionCommitQueueCapacity) { - this.shardTransactionCommitQueueCapacity = shardTransactionCommitQueueCapacity; + datastoreContext.shardTransactionCommitQueueCapacity = shardTransactionCommitQueueCapacity; return this; } public Builder shardInitializationTimeout(long timeout, TimeUnit unit) { - this.shardInitializationTimeout = new Timeout(timeout, unit); + datastoreContext.shardInitializationTimeout = new Timeout(timeout, unit); return this; } public Builder shardLeaderElectionTimeout(long timeout, TimeUnit unit) { - this.shardLeaderElectionTimeout = new Timeout(timeout, unit); + datastoreContext.shardLeaderElectionTimeout = new Timeout(timeout, unit); return this; } public Builder configurationReader(ConfigurationReader configurationReader){ - this.configurationReader = configurationReader; + datastoreContext.configurationReader = configurationReader; return this; } public Builder persistent(boolean persistent){ - this.persistent = persistent; + datastoreContext.persistent = persistent; return this; } public Builder shardIsolatedLeaderCheckIntervalInMillis(int shardIsolatedLeaderCheckIntervalInMillis) { - this.shardIsolatedLeaderCheckIntervalInMillis = shardIsolatedLeaderCheckIntervalInMillis; + datastoreContext.setIsolatedLeaderCheckInterval(shardIsolatedLeaderCheckIntervalInMillis); return this; } public Builder shardElectionTimeoutFactor(long shardElectionTimeoutFactor){ - this.shardElectionTimeoutFactor = shardElectionTimeoutFactor; + datastoreContext.setElectionTimeoutFactor(shardElectionTimeoutFactor); return this; } + public Builder transactionCreationInitialRateLimit(long initialRateLimit){ + datastoreContext.transactionCreationInitialRateLimit = initialRateLimit; + return this; + } - public DatastoreContext build() { - DefaultConfigParamsImpl raftConfig = new DefaultConfigParamsImpl(); - raftConfig.setHeartBeatInterval(new FiniteDuration(shardHeartbeatIntervalInMillis, - TimeUnit.MILLISECONDS)); - raftConfig.setJournalRecoveryLogBatchSize(shardJournalRecoveryLogBatchSize); - raftConfig.setSnapshotBatchCount(shardSnapshotBatchCount); - raftConfig.setSnapshotDataThresholdPercentage(shardSnapshotDataThresholdPercentage); - raftConfig.setElectionTimeoutFactor(shardElectionTimeoutFactor); - raftConfig.setIsolatedLeaderCheckInterval( - new FiniteDuration(shardIsolatedLeaderCheckIntervalInMillis, TimeUnit.MILLISECONDS)); + public Builder dataStoreType(String dataStoreType){ + datastoreContext.dataStoreType = dataStoreType; + datastoreContext.dataStoreMXBeanType = "Distributed" + WordUtils.capitalize(dataStoreType) + "Datastore"; + return this; + } - return new DatastoreContext(dataStoreProperties, raftConfig, dataStoreMXBeanType, - operationTimeoutInSeconds, shardTransactionIdleTimeout, - shardTransactionCommitTimeoutInSeconds, shardTransactionCommitQueueCapacity, - shardInitializationTimeout, shardLeaderElectionTimeout, - persistent, configurationReader, shardElectionTimeoutFactor); + public DatastoreContext build() { + return datastoreContext; } } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStore.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStore.java index 930c5f7257..107c959112 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStore.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStore.java @@ -39,20 +39,21 @@ public class DistributedDataStore implements DOMStore, SchemaContextListener, Au private final ActorContext actorContext; - public DistributedDataStore(ActorSystem actorSystem, String type, ClusterWrapper cluster, + public DistributedDataStore(ActorSystem actorSystem, ClusterWrapper cluster, Configuration configuration, DatastoreContext datastoreContext) { Preconditions.checkNotNull(actorSystem, "actorSystem should not be null"); - Preconditions.checkNotNull(type, "type should not be null"); Preconditions.checkNotNull(cluster, "cluster should not be null"); Preconditions.checkNotNull(configuration, "configuration should not be null"); Preconditions.checkNotNull(datastoreContext, "datastoreContext should not be null"); + String type = datastoreContext.getDataStoreType(); + String shardManagerId = ShardManagerIdentifier.builder().type(type).build().toString(); LOG.info("Creating ShardManager : {}", shardManagerId); actorContext = new ActorContext(actorSystem, actorSystem.actorOf( - ShardManager.props(type, cluster, configuration, datastoreContext) + ShardManager.props(cluster, configuration, datastoreContext) .withMailbox(ActorContext.MAILBOX), shardManagerId ), cluster, configuration, datastoreContext); } @@ -94,11 +95,13 @@ public class DistributedDataStore implements DOMStore, SchemaContextListener, Au @Override public DOMStoreWriteTransaction newWriteOnlyTransaction() { + actorContext.acquireTxCreationPermit(); return new TransactionProxy(actorContext, TransactionProxy.TransactionType.WRITE_ONLY); } @Override public DOMStoreReadWriteTransaction newReadWriteTransaction() { + actorContext.acquireTxCreationPermit(); return new TransactionProxy(actorContext, TransactionProxy.TransactionType.READ_WRITE); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreFactory.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreFactory.java index 5d63c92e88..a9a735ede7 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreFactory.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreFactory.java @@ -22,13 +22,13 @@ public class DistributedDataStoreFactory { private static volatile ActorSystem persistentActorSystem = null; - public static DistributedDataStore createInstance(String name, SchemaService schemaService, + public static DistributedDataStore createInstance(SchemaService schemaService, DatastoreContext datastoreContext, BundleContext bundleContext) { ActorSystem actorSystem = getOrCreateInstance(bundleContext, datastoreContext.getConfigurationReader()); Configuration config = new ConfigurationImpl("module-shards.conf", "modules.conf"); final DistributedDataStore dataStore = - new DistributedDataStore(actorSystem, name, new ClusterWrapperImpl(actorSystem), + new DistributedDataStore(actorSystem, new ClusterWrapperImpl(actorSystem), config, datastoreContext); ShardStrategyFactory.setConfiguration(config); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java index 9cd758ba30..21d74a6e1a 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/Shard.java @@ -12,8 +12,6 @@ import akka.actor.ActorRef; import akka.actor.ActorSelection; import akka.actor.Cancellable; import akka.actor.Props; -import akka.event.Logging; -import akka.event.LoggingAdapter; import akka.japi.Creator; import akka.persistence.RecoveryFailure; import akka.serialization.Serialization; @@ -101,9 +99,6 @@ public class Shard extends RaftActor { // The state of this Shard private final InMemoryDOMDataStore store; - private final LoggingAdapter LOG = - Logging.getLogger(getContext().system(), this); - /// The name of this shard private final ShardIdentifier name; @@ -148,7 +143,7 @@ public class Shard extends RaftActor { this.schemaContext = schemaContext; this.dataPersistenceProvider = (datastoreContext.isPersistent()) ? new PersistentDataProvider() : new NonPersistentRaftDataProvider(); - LOG.info("Shard created : {} persistent : {}", name, datastoreContext.isPersistent()); + LOG.info("Shard created : {}, persistent : {}", name, datastoreContext.isPersistent()); store = InMemoryDOMDataStoreFactory.create(name.toString(), null, datastoreContext.getDataStoreProperties()); @@ -166,7 +161,7 @@ public class Shard extends RaftActor { } commitCoordinator = new ShardCommitCoordinator(TimeUnit.SECONDS.convert(1, TimeUnit.MINUTES), - datastoreContext.getShardTransactionCommitQueueCapacity()); + datastoreContext.getShardTransactionCommitQueueCapacity(), LOG, name.toString()); transactionCommitTimeout = TimeUnit.MILLISECONDS.convert( datastoreContext.getShardTransactionCommitTimeoutInSeconds(), TimeUnit.SECONDS); @@ -216,13 +211,13 @@ public class Shard extends RaftActor { @Override public void onReceiveRecover(final Object message) throws Exception { if(LOG.isDebugEnabled()) { - LOG.debug("onReceiveRecover: Received message {} from {}", - message.getClass().toString(), - getSender()); + LOG.debug("{}: onReceiveRecover: Received message {} from {}", persistenceId(), + message.getClass().toString(), getSender()); } if (message instanceof RecoveryFailure){ - LOG.error(((RecoveryFailure) message).cause(), "Recovery failed because of this cause"); + LOG.error("{}: Recovery failed because of this cause", + persistenceId(), ((RecoveryFailure) message).cause()); // Even though recovery failed, we still need to finish our recovery, eg send the // ActorInitialized message and start the txCommitTimeoutCheckSchedule. @@ -234,10 +229,6 @@ public class Shard extends RaftActor { @Override public void onReceiveCommand(final Object message) throws Exception { - if(LOG.isDebugEnabled()) { - LOG.debug("onReceiveCommand: Received message {} from {}", message, getSender()); - } - if (message.getClass().equals(CreateTransaction.SERIALIZABLE_CLASS)) { handleCreateTransaction(message); } else if(message instanceof ForwardedReadyTransaction) { @@ -275,8 +266,8 @@ public class Shard extends RaftActor { if(cohortEntry != null) { long elapsed = System.currentTimeMillis() - cohortEntry.getLastAccessTime(); if(elapsed > transactionCommitTimeout) { - LOG.warning("Current transaction {} has timed out after {} ms - aborting", - cohortEntry.getTransactionID(), transactionCommitTimeout); + LOG.warn("{}: Current transaction {} has timed out after {} ms - aborting", + persistenceId(), cohortEntry.getTransactionID(), transactionCommitTimeout); doAbortTransaction(cohortEntry.getTransactionID(), null); } @@ -286,7 +277,7 @@ public class Shard extends RaftActor { private void handleCommitTransaction(final CommitTransaction commit) { final String transactionID = commit.getTransactionID(); - LOG.debug("Committing transaction {}", transactionID); + LOG.debug("{}: Committing transaction {}", persistenceId(), transactionID); // Get the current in-progress cohort entry in the commitCoordinator if it corresponds to // this transaction. @@ -295,8 +286,8 @@ public class Shard extends RaftActor { // We're not the current Tx - the Tx was likely expired b/c it took too long in // between the canCommit and commit messages. IllegalStateException ex = new IllegalStateException( - String.format("Cannot commit transaction %s - it is not the current transaction", - transactionID)); + String.format("%s: Cannot commit transaction %s - it is not the current transaction", + persistenceId(), transactionID)); LOG.error(ex.getMessage()); shardMBean.incrementFailedTransactionsCount(); getSender().tell(new akka.actor.Status.Failure(ex), getSelf()); @@ -322,9 +313,9 @@ public class Shard extends RaftActor { Shard.this.persistData(getSender(), transactionID, new ModificationPayload(cohortEntry.getModification())); } - } catch (InterruptedException | ExecutionException | IOException e) { - LOG.error(e, "An exception occurred while preCommitting transaction {}", - cohortEntry.getTransactionID()); + } catch (Exception e) { + LOG.error("{} An exception occurred while preCommitting transaction {}", + persistenceId(), cohortEntry.getTransactionID(), e); shardMBean.incrementFailedTransactionsCount(); getSender().tell(new akka.actor.Status.Failure(e), getSelf()); } @@ -352,8 +343,8 @@ public class Shard extends RaftActor { // This really shouldn't happen - it likely means that persistence or replication // took so long to complete such that the cohort entry was expired from the cache. IllegalStateException ex = new IllegalStateException( - String.format("Could not finish committing transaction %s - no CohortEntry found", - transactionID)); + String.format("%s: Could not finish committing transaction %s - no CohortEntry found", + persistenceId(), transactionID)); LOG.error(ex.getMessage()); sender.tell(new akka.actor.Status.Failure(ex), getSelf()); } @@ -361,7 +352,7 @@ public class Shard extends RaftActor { return; } - LOG.debug("Finishing commit for transaction {}", cohortEntry.getTransactionID()); + LOG.debug("{}: Finishing commit for transaction {}", persistenceId(), cohortEntry.getTransactionID()); try { // We block on the future here so we don't have to worry about possibly accessing our @@ -374,24 +365,25 @@ public class Shard extends RaftActor { shardMBean.incrementCommittedTransactionCount(); shardMBean.setLastCommittedTransactionTime(System.currentTimeMillis()); - } catch (InterruptedException | ExecutionException e) { + } catch (Exception e) { sender.tell(new akka.actor.Status.Failure(e), getSelf()); - LOG.error(e, "An exception occurred while committing transaction {}", transactionID); + LOG.error("{}, An exception occurred while committing transaction {}", persistenceId(), + transactionID, e); shardMBean.incrementFailedTransactionsCount(); + } finally { + commitCoordinator.currentTransactionComplete(transactionID, true); } - - commitCoordinator.currentTransactionComplete(transactionID, true); } private void handleCanCommitTransaction(final CanCommitTransaction canCommit) { - LOG.debug("Can committing transaction {}", canCommit.getTransactionID()); + LOG.debug("{}: Can committing transaction {}", persistenceId(), canCommit.getTransactionID()); commitCoordinator.handleCanCommit(canCommit, getSender(), self()); } private void handleForwardedReadyTransaction(ForwardedReadyTransaction ready) { - LOG.debug("Readying transaction {}, client version {}", ready.getTransactionID(), - ready.getTxnClientVersion()); + LOG.debug("{}: Readying transaction {}, client version {}", persistenceId(), + ready.getTransactionID(), ready.getTxnClientVersion()); // This message is forwarded by the ShardTransaction on ready. We cache the cohort in the // commitCoordinator in preparation for the subsequent three phase commit initiated by @@ -406,7 +398,7 @@ public class Shard extends RaftActor { // to provide the compatible behavior. ActorRef replyActorPath = self(); if(ready.getTxnClientVersion() < DataStoreVersions.HELIUM_1_VERSION) { - LOG.debug("Creating BackwardsCompatibleThreePhaseCommitCohort"); + LOG.debug("{}: Creating BackwardsCompatibleThreePhaseCommitCohort", persistenceId()); replyActorPath = getContext().actorOf(BackwardsCompatibleThreePhaseCommitCohort.props( ready.getTransactionID())); } @@ -424,7 +416,7 @@ public class Shard extends RaftActor { void doAbortTransaction(final String transactionID, final ActorRef sender) { final CohortEntry cohortEntry = commitCoordinator.getCohortEntryIfCurrent(transactionID); if(cohortEntry != null) { - LOG.debug("Aborting transaction {}", transactionID); + LOG.debug("{}: Aborting transaction {}", persistenceId(), transactionID); // We don't remove the cached cohort entry here (ie pass false) in case the Tx was // aborted during replication in which case we may still commit locally if replication @@ -446,7 +438,7 @@ public class Shard extends RaftActor { @Override public void onFailure(final Throwable t) { - LOG.error(t, "An exception happened during abort"); + LOG.error("{}: An exception happened during abort", persistenceId(), t); if(sender != null) { sender.tell(new akka.actor.Status.Failure(t), self); @@ -462,10 +454,10 @@ public class Shard extends RaftActor { } else if (getLeader() != null) { getLeader().forward(message, getContext()); } else { - getSender().tell(new akka.actor.Status.Failure(new NoShardLeaderException( - "Could not find shard leader so transaction cannot be created. This typically happens" + + getSender().tell(new akka.actor.Status.Failure(new NoShardLeaderException(String.format( + "Could not find leader for shard %s so transaction cannot be created. This typically happens" + " when the system is coming up or recovering and a leader is being elected. Try again" + - " later.")), getSelf()); + " later.", persistenceId()))), getSelf()); } } @@ -556,7 +548,7 @@ public class Shard extends RaftActor { .build(); if(LOG.isDebugEnabled()) { - LOG.debug("Creating transaction : {} ", transactionId); + LOG.debug("{}: Creating transaction : {} ", persistenceId(), transactionId); } ActorRef transactionActor = createTypedTransactionActor(transactionType, transactionId, @@ -581,7 +573,7 @@ public class Shard extends RaftActor { shardMBean.setLastCommittedTransactionTime(System.currentTimeMillis()); } catch (InterruptedException | ExecutionException e) { shardMBean.incrementFailedTransactionsCount(); - LOG.error(e, "Failed to commit"); + LOG.error("{}: Failed to commit", persistenceId(), e); } } @@ -598,14 +590,14 @@ public class Shard extends RaftActor { private void registerChangeListener(final RegisterChangeListener registerChangeListener) { - LOG.debug("registerDataChangeListener for {}", registerChangeListener.getPath()); + LOG.debug("{}: registerDataChangeListener for {}", persistenceId(), registerChangeListener.getPath()); ListenerRegistration>> registration; if(isLeader()) { registration = doChangeListenerRegistration(registerChangeListener); } else { - LOG.debug("Shard is not the leader - delaying registration"); + LOG.debug("{}: Shard is not the leader - delaying registration", persistenceId()); DelayedListenerRegistration delayedReg = new DelayedListenerRegistration(registerChangeListener); @@ -616,8 +608,8 @@ public class Shard extends RaftActor { ActorRef listenerRegistration = getContext().actorOf( DataChangeListenerRegistration.props(registration)); - LOG.debug("registerDataChangeListener sending reply, listenerRegistrationPath = {} ", - listenerRegistration.path()); + LOG.debug("{}: registerDataChangeListener sending reply, listenerRegistrationPath = {} ", + persistenceId(), listenerRegistration.path()); getSender().tell(new RegisterChangeListenerReply(listenerRegistration.path()), getSelf()); } @@ -641,7 +633,7 @@ public class Shard extends RaftActor { AsyncDataChangeListener> listener = new DataChangeListenerProxy(dataChangeListenerPath); - LOG.debug("Registering for path {}", registerChangeListener.getPath()); + LOG.debug("{}: Registering for path {}", persistenceId(), registerChangeListener.getPath()); return store.registerChangeListener(registerChangeListener.getPath(), listener, registerChangeListener.getScope()); @@ -658,7 +650,7 @@ public class Shard extends RaftActor { currentLogRecoveryBatch = Lists.newArrayListWithCapacity(maxBatchSize); if(LOG.isDebugEnabled()) { - LOG.debug("{} : starting log recovery batch with max size {}", persistenceId(), maxBatchSize); + LOG.debug("{}: starting log recovery batch with max size {}", persistenceId(), maxBatchSize); } } @@ -668,40 +660,42 @@ public class Shard extends RaftActor { try { currentLogRecoveryBatch.add(((ModificationPayload) data).getModification()); } catch (ClassNotFoundException | IOException e) { - LOG.error(e, "Error extracting ModificationPayload"); + LOG.error("{}: Error extracting ModificationPayload", persistenceId(), e); } } else if (data instanceof CompositeModificationPayload) { currentLogRecoveryBatch.add(((CompositeModificationPayload) data).getModification()); } else if (data instanceof CompositeModificationByteStringPayload) { currentLogRecoveryBatch.add(((CompositeModificationByteStringPayload) data).getModification()); } else { - LOG.error("Unknown state received {} during recovery", data); + LOG.error("{}: Unknown state received {} during recovery", persistenceId(), data); } } @Override protected void applyRecoverySnapshot(final byte[] snapshotBytes) { if(recoveryCoordinator == null) { - recoveryCoordinator = new ShardRecoveryCoordinator(persistenceId(), schemaContext); + recoveryCoordinator = new ShardRecoveryCoordinator(persistenceId(), schemaContext, + LOG, name.toString()); } recoveryCoordinator.submit(snapshotBytes, store.newWriteOnlyTransaction()); if(LOG.isDebugEnabled()) { - LOG.debug("{} : submitted recovery sbapshot", persistenceId()); + LOG.debug("{}: submitted recovery sbapshot", persistenceId()); } } @Override protected void applyCurrentLogRecoveryBatch() { if(recoveryCoordinator == null) { - recoveryCoordinator = new ShardRecoveryCoordinator(persistenceId(), schemaContext); + recoveryCoordinator = new ShardRecoveryCoordinator(persistenceId(), schemaContext, + LOG, name.toString()); } recoveryCoordinator.submit(currentLogRecoveryBatch, store.newWriteOnlyTransaction()); if(LOG.isDebugEnabled()) { - LOG.debug("{} : submitted log recovery batch with size {}", persistenceId(), + LOG.debug("{}: submitted log recovery batch with size {}", persistenceId(), currentLogRecoveryBatch.size()); } } @@ -712,7 +706,7 @@ public class Shard extends RaftActor { Collection txList = recoveryCoordinator.getTransactions(); if(LOG.isDebugEnabled()) { - LOG.debug("{} : recovery complete - committing {} Tx's", persistenceId(), txList.size()); + LOG.debug("{}: recovery complete - committing {} Tx's", persistenceId(), txList.size()); } for(DOMStoreWriteTransaction tx: txList) { @@ -721,7 +715,7 @@ public class Shard extends RaftActor { shardMBean.incrementCommittedTransactionCount(); } catch (InterruptedException | ExecutionException e) { shardMBean.incrementFailedTransactionsCount(); - LOG.error(e, "Failed to commit"); + LOG.error("{}: Failed to commit", persistenceId(), e); } } } @@ -751,7 +745,7 @@ public class Shard extends RaftActor { try { applyModificationToState(clientActor, identifier, ((ModificationPayload) data).getModification()); } catch (ClassNotFoundException | IOException e) { - LOG.error(e, "Error extracting ModificationPayload"); + LOG.error("{}: Error extracting ModificationPayload", persistenceId(), e); } } else if (data instanceof CompositeModificationPayload) { @@ -763,8 +757,8 @@ public class Shard extends RaftActor { applyModificationToState(clientActor, identifier, modification); } else { - LOG.error("Unknown state received {} Class loader = {} CompositeNodeMod.ClassLoader = {}", - data, data.getClass().getClassLoader(), + LOG.error("{}: Unknown state received {} Class loader = {} CompositeNodeMod.ClassLoader = {}", + persistenceId(), data, data.getClass().getClassLoader(), CompositeModificationPayload.class.getClassLoader()); } @@ -775,8 +769,8 @@ public class Shard extends RaftActor { private void applyModificationToState(ActorRef clientActor, String identifier, Object modification) { if(modification == null) { LOG.error( - "modification is null - this is very unexpected, clientActor = {}, identifier = {}", - identifier, clientActor != null ? clientActor.path().toString() : null); + "{}: modification is null - this is very unexpected, clientActor = {}, identifier = {}", + persistenceId(), identifier, clientActor != null ? clientActor.path().toString() : null); } else if(clientActor == null) { // There's no clientActor to which to send a commit reply so we must be applying // replicated state from the leader. @@ -821,7 +815,7 @@ public class Shard extends RaftActor { // we can safely commit everything in here. We not need to worry about event notifications // as they would have already been disabled on the follower - LOG.info("Applying snapshot"); + LOG.info("{}: Applying snapshot", persistenceId()); try { DOMStoreWriteTransaction transaction = store.newWriteOnlyTransaction(); @@ -834,9 +828,9 @@ public class Shard extends RaftActor { transaction.write(DATASTORE_ROOT, node); syncCommitTransaction(transaction); } catch (InterruptedException | ExecutionException e) { - LOG.error(e, "An exception occurred when applying snapshot"); + LOG.error("{}: An exception occurred when applying snapshot", persistenceId(), e); } finally { - LOG.info("Done applying snapshot"); + LOG.info("{}: Done applying snapshot", persistenceId()); } } @@ -865,8 +859,8 @@ public class Shard extends RaftActor { for(Map.Entry entry : transactionChains.entrySet()){ if(LOG.isDebugEnabled()) { LOG.debug( - "onStateChanged: Closing transaction chain {} because shard {} is no longer the leader", - entry.getKey(), getId()); + "{}: onStateChanged: Closing transaction chain {} because shard {} is no longer the leader", + persistenceId(), entry.getKey(), getId()); } entry.getValue().close(); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardCommitCoordinator.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardCommitCoordinator.java index 19fa26682e..8b95404c4e 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardCommitCoordinator.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardCommitCoordinator.java @@ -20,7 +20,6 @@ import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransacti import org.opendaylight.controller.cluster.datastore.modification.Modification; import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Coordinates commits for a shard ensuring only one concurrent 3-phase commit. @@ -29,8 +28,6 @@ import org.slf4j.LoggerFactory; */ public class ShardCommitCoordinator { - private static final Logger LOG = LoggerFactory.getLogger(ShardCommitCoordinator.class); - private final Cache cohortCache; private CohortEntry currentCohortEntry; @@ -39,11 +36,18 @@ public class ShardCommitCoordinator { private final int queueCapacity; - public ShardCommitCoordinator(long cacheExpiryTimeoutInSec, int queueCapacity) { + private final Logger log; + + private final String name; + + public ShardCommitCoordinator(long cacheExpiryTimeoutInSec, int queueCapacity, Logger log, + String name) { cohortCache = CacheBuilder.newBuilder().expireAfterAccess( cacheExpiryTimeoutInSec, TimeUnit.SECONDS).build(); this.queueCapacity = queueCapacity; + this.log = log; + this.name = name; // We use a LinkedList here to avoid synchronization overhead with concurrent queue impls // since this should only be accessed on the shard's dispatcher. @@ -74,9 +78,9 @@ public class ShardCommitCoordinator { public void handleCanCommit(CanCommitTransaction canCommit, final ActorRef sender, final ActorRef shard) { String transactionID = canCommit.getTransactionID(); - if(LOG.isDebugEnabled()) { - LOG.debug("Processing canCommit for transaction {} for shard {}", - transactionID, shard.path()); + if(log.isDebugEnabled()) { + log.debug("{}: Processing canCommit for transaction {} for shard {}", + name, transactionID, shard.path()); } // Lookup the cohort entry that was cached previously (or should have been) by @@ -86,8 +90,8 @@ public class ShardCommitCoordinator { // Either canCommit was invoked before ready(shouldn't happen) or a long time passed // between canCommit and ready and the entry was expired from the cache. IllegalStateException ex = new IllegalStateException( - String.format("No cohort entry found for transaction %s", transactionID)); - LOG.error(ex.getMessage()); + String.format("%s: No cohort entry found for transaction %s", name, transactionID)); + log.error(ex.getMessage()); sender.tell(new Status.Failure(ex), shard); return; } @@ -98,8 +102,8 @@ public class ShardCommitCoordinator { if(currentCohortEntry != null) { // There's already a Tx commit in progress - attempt to queue this entry to be // committed after the current Tx completes. - LOG.debug("Transaction {} is already in progress - queueing transaction {}", - currentCohortEntry.getTransactionID(), transactionID); + log.debug("{}: Transaction {} is already in progress - queueing transaction {}", + name, currentCohortEntry.getTransactionID(), transactionID); if(queuedCohortEntries.size() < queueCapacity) { queuedCohortEntries.offer(cohortEntry); @@ -107,10 +111,10 @@ public class ShardCommitCoordinator { removeCohortEntry(transactionID); RuntimeException ex = new RuntimeException( - String.format("Could not enqueue transaction %s - the maximum commit queue"+ + String.format("%s: Could not enqueue transaction %s - the maximum commit queue"+ " capacity %d has been reached.", - transactionID, queueCapacity)); - LOG.error(ex.getMessage()); + name, transactionID, queueCapacity)); + log.error(ex.getMessage()); sender.tell(new Status.Failure(ex), shard); } } else { @@ -140,7 +144,7 @@ public class ShardCommitCoordinator { removeCohortEntry(cohortEntry.getTransactionID()); } } catch (InterruptedException | ExecutionException e) { - LOG.debug("An exception occurred during canCommit", e); + log.debug("{}: An exception occurred during canCommit: {}", name, e); // Remove the entry from the cache now since the Tx will be aborted. removeCohortEntry(cohortEntry.getTransactionID()); @@ -201,6 +205,7 @@ public class ShardCommitCoordinator { // Dequeue the next cohort entry waiting in the queue. currentCohortEntry = queuedCohortEntries.poll(); if(currentCohortEntry != null) { + currentCohortEntry.updateLastAccessTime(); doCanCommit(currentCohortEntry); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardManager.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardManager.java index 10876045ae..d52965e055 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardManager.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardManager.java @@ -15,8 +15,6 @@ import akka.actor.OneForOneStrategy; import akka.actor.Props; import akka.actor.SupervisorStrategy; import akka.cluster.ClusterEvent; -import akka.event.Logging; -import akka.event.LoggingAdapter; import akka.japi.Creator; import akka.japi.Function; import akka.japi.Procedure; @@ -27,6 +25,15 @@ import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.opendaylight.controller.cluster.DataPersistenceProvider; import org.opendaylight.controller.cluster.common.actor.AbstractUntypedPersistentActorWithMetering; import org.opendaylight.controller.cluster.datastore.identifiers.ShardIdentifier; @@ -45,16 +52,9 @@ import org.opendaylight.controller.cluster.datastore.messages.PrimaryNotFound; import org.opendaylight.controller.cluster.datastore.messages.UpdateSchemaContext; import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import scala.concurrent.duration.Duration; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; /** * The ShardManager has the following jobs, @@ -67,8 +67,7 @@ import java.util.Set; */ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { - protected final LoggingAdapter LOG = - Logging.getLogger(getContext().system(), this); + private final Logger LOG = LoggerFactory.getLogger(getClass()); // Stores a mapping between a member name and the address of the member // Member names look like "member-1", "member-2" etc and are as specified @@ -97,17 +96,15 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { private final DataPersistenceProvider dataPersistenceProvider; /** - * @param type defines the kind of data that goes into shards created by this shard manager. Examples of type would be - * configuration or operational */ - protected ShardManager(String type, ClusterWrapper cluster, Configuration configuration, + protected ShardManager(ClusterWrapper cluster, Configuration configuration, DatastoreContext datastoreContext) { - this.type = Preconditions.checkNotNull(type, "type should not be null"); this.cluster = Preconditions.checkNotNull(cluster, "cluster should not be null"); this.configuration = Preconditions.checkNotNull(configuration, "configuration should not be null"); this.datastoreContext = datastoreContext; this.dataPersistenceProvider = createDataPersistenceProvider(datastoreContext.isPersistent()); + this.type = datastoreContext.getDataStoreType(); // Subscribe this actor to cluster member events cluster.subscribeToMemberEvents(getSelf()); @@ -119,16 +116,15 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { return (persistent) ? new PersistentDataProvider() : new NonPersistentDataProvider(); } - public static Props props(final String type, + public static Props props( final ClusterWrapper cluster, final Configuration configuration, final DatastoreContext datastoreContext) { - Preconditions.checkNotNull(type, "type should not be null"); Preconditions.checkNotNull(cluster, "cluster should not be null"); Preconditions.checkNotNull(configuration, "configuration should not be null"); - return Props.create(new ShardManagerCreator(type, cluster, configuration, datastoreContext)); + return Props.create(new ShardManagerCreator(cluster, configuration, datastoreContext)); } @Override @@ -186,7 +182,7 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { knownModules = ImmutableSet.copyOf(msg.getModules()); } else if (message instanceof RecoveryFailure) { RecoveryFailure failure = (RecoveryFailure) message; - LOG.error(failure.cause(), "Recovery failed"); + LOG.error("Recovery failed", failure.cause()); } else if (message instanceof RecoveryCompleted) { LOG.info("Recovery complete : {}", persistenceId()); @@ -275,7 +271,7 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { if(newModules.containsAll(knownModules)) { - LOG.info("New SchemaContext has a super set of current knownModules - persisting info"); + LOG.debug("New SchemaContext has a super set of current knownModules - persisting info"); knownModules = ImmutableSet.copyOf(newModules); @@ -283,7 +279,7 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { @Override public void apply(SchemaContextModules param) throws Exception { - LOG.info("Sending new SchemaContext to Shards"); + LOG.debug("Sending new SchemaContext to Shards"); for (ShardInformation info : localShards.values()) { if (info.getActor() == null) { info.setActor(getContext().actorOf(Shard.props(info.getShardId(), @@ -297,7 +293,8 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { }); } else { - LOG.info("Rejecting schema context update because it is not a super set of previously known modules"); + LOG.debug("Rejecting schema context update - not a super set of previously known modules:\nUPDATE: {}\nKNOWN: {}", + newModules, knownModules); } } @@ -424,12 +421,7 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { new Function() { @Override public SupervisorStrategy.Directive apply(Throwable t) { - StringBuilder sb = new StringBuilder(); - for(StackTraceElement element : t.getStackTrace()) { - sb.append("\n\tat ") - .append(element.toString()); - } - LOG.warning("Supervisor Strategy of resume applied {}",sb.toString()); + LOG.warn("Supervisor Strategy caught unexpected exception - resuming", t); return SupervisorStrategy.resume(); } } @@ -535,14 +527,12 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { private static class ShardManagerCreator implements Creator { private static final long serialVersionUID = 1L; - final String type; final ClusterWrapper cluster; final Configuration configuration; final DatastoreContext datastoreContext; - ShardManagerCreator(String type, ClusterWrapper cluster, + ShardManagerCreator(ClusterWrapper cluster, Configuration configuration, DatastoreContext datastoreContext) { - this.type = type; this.cluster = cluster; this.configuration = configuration; this.datastoreContext = datastoreContext; @@ -550,12 +540,13 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { @Override public ShardManager create() throws Exception { - return new ShardManager(type, cluster, configuration, datastoreContext); + return new ShardManager(cluster, configuration, datastoreContext); } } static class SchemaContextModules implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -8884620101025936590L; + private final Set modules; SchemaContextModules(Set modules){ diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java index 6f8d0567d9..2e66ef918e 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardReadTransaction.java @@ -96,4 +96,9 @@ public class ShardReadTransaction extends ShardTransaction { protected DOMStoreTransaction getDOMStoreTransaction() { return transaction; } + + @Override + protected boolean returnCloseTransactionReply() { + return false; + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinator.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinator.java index 238b4e46dc..50528575e7 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinator.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardRecoveryCoordinator.java @@ -22,7 +22,6 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Coordinates persistence recovery of journal log entries and snapshots for a shard. Each snapshot @@ -37,16 +36,19 @@ class ShardRecoveryCoordinator { private static final int TIME_OUT = 10; - private static final Logger LOG = LoggerFactory.getLogger(ShardRecoveryCoordinator.class); - private final List resultingTxList = Lists.newArrayList(); private final SchemaContext schemaContext; private final String shardName; private final ExecutorService executor; + private final Logger log; + private final String name; - ShardRecoveryCoordinator(String shardName, SchemaContext schemaContext) { + ShardRecoveryCoordinator(String shardName, SchemaContext schemaContext, Logger log, + String name) { this.schemaContext = schemaContext; this.shardName = shardName; + this.log = log; + this.name = name; executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder().setDaemon(true) @@ -85,7 +87,7 @@ class ShardRecoveryCoordinator { if(executor.awaitTermination(TIME_OUT, TimeUnit.MINUTES)) { return resultingTxList; } else { - LOG.error("Recovery for shard {} timed out after {} minutes", shardName, TIME_OUT); + log.error("{}: Recovery for shard {} timed out after {} minutes", name, shardName, TIME_OUT); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java index 678b781569..af25df13d2 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ShardTransaction.java @@ -39,12 +39,6 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; * The ShardTransaction Actor delegates all actions to DOMDataReadWriteTransaction *

*

- * Even though the DOMStore and the DOMStoreTransactionChain implement multiple types of transactions - * the ShardTransaction Actor only works with read-write transactions. This is just to keep the logic simple. At this - * time there are no known advantages for creating a read-only or write-only transaction which may change over time - * at which point we can optimize things in the distributed store as well. - *

- *

* Handles Messages
* ----------------
*

  • {@link org.opendaylight.controller.cluster.datastore.messages.ReadData} @@ -114,10 +108,14 @@ public abstract class ShardTransaction extends AbstractUntypedActorWithMetering } } + protected boolean returnCloseTransactionReply() { + return true; + } + private void closeTransaction(boolean sendReply) { getDOMStoreTransaction().close(); - if(sendReply) { + if(sendReply && returnCloseTransactionReply()) { getSender().tell(CloseTransactionReply.INSTANCE.toSerializable(), getSelf()); } @@ -126,29 +124,20 @@ public abstract class ShardTransaction extends AbstractUntypedActorWithMetering protected void readData(DOMStoreReadTransaction transaction, ReadData message, final boolean returnSerialized) { - final ActorRef sender = getSender(); - final ActorRef self = getSelf(); - final YangInstanceIdentifier path = message.getPath(); - final CheckedFuture>, ReadFailedException> future = - transaction.read(path); - future.addListener(new Runnable() { - @Override - public void run() { - try { - Optional> optional = future.checkedGet(); - ReadDataReply readDataReply = new ReadDataReply(optional.orNull()); - - sender.tell((returnSerialized ? readDataReply.toSerializable(clientTxVersion): - readDataReply), self); + final YangInstanceIdentifier path = message.getPath(); + try { + final CheckedFuture>, ReadFailedException> future = transaction.read(path); + Optional> optional = future.checkedGet(); + ReadDataReply readDataReply = new ReadDataReply(optional.orNull()); - } catch (Exception e) { - shardStats.incrementFailedReadTransactionsCount(); - sender.tell(new akka.actor.Status.Failure(e), self); - } + sender().tell((returnSerialized ? readDataReply.toSerializable(clientTxVersion): readDataReply), self()); - } - }, getContext().dispatcher()); + } catch (Exception e) { + LOG.debug(String.format("Unexpected error reading path %s", path), e); + shardStats.incrementFailedReadTransactionsCount(); + sender().tell(new akka.actor.Status.Failure(e), self()); + } } protected void dataExists(DOMStoreReadTransaction transaction, DataExists message, diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TerminationMonitor.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TerminationMonitor.java index 0c3d33a78c..6dd0ab1230 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TerminationMonitor.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TerminationMonitor.java @@ -10,16 +10,15 @@ package org.opendaylight.controller.cluster.datastore; import akka.actor.Terminated; import akka.actor.UntypedActor; -import akka.event.Logging; -import akka.event.LoggingAdapter; import org.opendaylight.controller.cluster.datastore.messages.Monitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class TerminationMonitor extends UntypedActor{ - protected final LoggingAdapter LOG = - Logging.getLogger(getContext().system(), this); + private static final Logger LOG = LoggerFactory.getLogger(TerminationMonitor.class); public TerminationMonitor(){ - LOG.info("Created TerminationMonitor"); + LOG.debug("Created TerminationMonitor"); } @Override public void onReceive(Object message) throws Exception { diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ThreePhaseCommitCohortProxy.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ThreePhaseCommitCohortProxy.java index 932c36fe34..c51ea80726 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ThreePhaseCommitCohortProxy.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/ThreePhaseCommitCohortProxy.java @@ -11,12 +11,15 @@ package org.opendaylight.controller.cluster.datastore; import akka.actor.ActorSelection; import akka.dispatch.Futures; import akka.dispatch.OnComplete; +import com.codahale.metrics.Snapshot; +import com.codahale.metrics.Timer; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import org.opendaylight.controller.cluster.datastore.messages.AbortTransaction; import org.opendaylight.controller.cluster.datastore.messages.AbortTransactionReply; import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransaction; @@ -44,6 +47,19 @@ public class ThreePhaseCommitCohortProxy implements DOMStoreThreePhaseCommitCoho private final List> cohortFutures; private volatile List cohorts; private final String transactionId; + private static final OperationCallback NO_OP_CALLBACK = new OperationCallback() { + @Override + public void run() { + } + + @Override + public void success() { + } + + @Override + public void failure() { + } + }; public ThreePhaseCommitCohortProxy(ActorContext actorContext, List> cohortFutures, String transactionId) { @@ -151,8 +167,7 @@ public class ThreePhaseCommitCohortProxy implements DOMStoreThreePhaseCommitCoho if(LOG.isDebugEnabled()) { LOG.debug("Tx {}: Sending {} to cohort {}", transactionId, message, cohort); } - - futureList.add(actorContext.executeOperationAsync(cohort, message)); + futureList.add(actorContext.executeOperationAsync(cohort, message, actorContext.getTransactionCommitOperationTimeout())); } return Futures.sequence(futureList, actorContext.getActorSystem().dispatcher()); @@ -179,12 +194,20 @@ public class ThreePhaseCommitCohortProxy implements DOMStoreThreePhaseCommitCoho @Override public ListenableFuture commit() { - return voidOperation("commit", new CommitTransaction(transactionId).toSerializable(), - CommitTransactionReply.SERIALIZABLE_CLASS, true); + OperationCallback operationCallback = (cohortFutures.size() == 0) ? NO_OP_CALLBACK : + new CommitCallback(actorContext); + + return voidOperation("commit", new CommitTransaction(transactionId).toSerializable(), + CommitTransactionReply.SERIALIZABLE_CLASS, true, operationCallback); + } + + private ListenableFuture voidOperation(final String operationName, final Object message, + final Class expectedResponseClass, final boolean propagateException) { + return voidOperation(operationName, message, expectedResponseClass, propagateException, NO_OP_CALLBACK); } private ListenableFuture voidOperation(final String operationName, final Object message, - final Class expectedResponseClass, final boolean propagateException) { + final Class expectedResponseClass, final boolean propagateException, final OperationCallback callback) { if(LOG.isDebugEnabled()) { LOG.debug("Tx {} {}", transactionId, operationName); @@ -196,7 +219,7 @@ public class ThreePhaseCommitCohortProxy implements DOMStoreThreePhaseCommitCoho if(cohorts != null) { finishVoidOperation(operationName, message, expectedResponseClass, propagateException, - returnFuture); + returnFuture, callback); } else { buildCohortList().onComplete(new OnComplete() { @Override @@ -213,7 +236,7 @@ public class ThreePhaseCommitCohortProxy implements DOMStoreThreePhaseCommitCoho } } else { finishVoidOperation(operationName, message, expectedResponseClass, - propagateException, returnFuture); + propagateException, returnFuture, callback); } } }, actorContext.getActorSystem().dispatcher()); @@ -223,11 +246,14 @@ public class ThreePhaseCommitCohortProxy implements DOMStoreThreePhaseCommitCoho } private void finishVoidOperation(final String operationName, final Object message, - final Class expectedResponseClass, final boolean propagateException, - final SettableFuture returnFuture) { + final Class expectedResponseClass, final boolean propagateException, + final SettableFuture returnFuture, final OperationCallback callback) { if(LOG.isDebugEnabled()) { LOG.debug("Tx {} finish {}", transactionId, operationName); } + + callback.run(); + Future> combinedFuture = invokeCohorts(message); combinedFuture.onComplete(new OnComplete>() { @@ -247,6 +273,7 @@ public class ThreePhaseCommitCohortProxy implements DOMStoreThreePhaseCommitCoho } if(exceptionToPropagate != null) { + if(LOG.isDebugEnabled()) { LOG.debug("Tx {}: a {} cohort Future failed: {}", transactionId, operationName, exceptionToPropagate); @@ -265,11 +292,16 @@ public class ThreePhaseCommitCohortProxy implements DOMStoreThreePhaseCommitCoho } returnFuture.set(null); } + + callback.failure(); } else { + if(LOG.isDebugEnabled()) { LOG.debug("Tx {}: {} succeeded", transactionId, operationName); } returnFuture.set(null); + + callback.success(); } } }, actorContext.getActorSystem().dispatcher()); @@ -279,4 +311,58 @@ public class ThreePhaseCommitCohortProxy implements DOMStoreThreePhaseCommitCoho List> getCohortFutures() { return Collections.unmodifiableList(cohortFutures); } + + private static interface OperationCallback { + void run(); + void success(); + void failure(); + } + + private static class CommitCallback implements OperationCallback{ + + private static final Logger LOG = LoggerFactory.getLogger(CommitCallback.class); + private static final String COMMIT = "commit"; + + private final Timer commitTimer; + private final ActorContext actorContext; + private Timer.Context timerContext; + + CommitCallback(ActorContext actorContext){ + this.actorContext = actorContext; + commitTimer = actorContext.getOperationTimer(COMMIT); + } + + @Override + public void run() { + timerContext = commitTimer.time(); + } + + @Override + public void success() { + timerContext.stop(); + + Snapshot timerSnapshot = commitTimer.getSnapshot(); + double allowedLatencyInNanos = timerSnapshot.get95thPercentile(); + + long commitTimeoutInSeconds = actorContext.getDatastoreContext() + .getShardTransactionCommitTimeoutInSeconds(); + long commitTimeoutInNanos = TimeUnit.SECONDS.toNanos(commitTimeoutInSeconds); + + // Here we are trying to find out how many transactions per second are allowed + double newRateLimit = ((double) commitTimeoutInNanos / allowedLatencyInNanos) / commitTimeoutInSeconds; + + LOG.debug("Data Store {} commit rateLimit adjusted to {} allowedLatencyInNanos = {}", + actorContext.getDataStoreType(), newRateLimit, allowedLatencyInNanos); + + actorContext.setTxCreationLimit(newRateLimit); + } + + @Override + public void failure() { + // This would mean we couldn't get a transaction completed in 30 seconds which is + // the default transaction commit timeout. Using the timeout information to figure out the rate limit is + // not going to be useful - so we leave it as it is + } + } + } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxy.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxy.java index 87959efe8a..ee3a5cc825 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxy.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxy.java @@ -104,11 +104,13 @@ public class TransactionChainProxy implements DOMStoreTransactionChain { @Override public DOMStoreReadWriteTransaction newReadWriteTransaction() { + actorContext.acquireTxCreationPermit(); return allocateWriteTransaction(TransactionProxy.TransactionType.READ_WRITE); } @Override public DOMStoreWriteTransaction newWriteOnlyTransaction() { + actorContext.acquireTxCreationPermit(); return allocateWriteTransaction(TransactionProxy.TransactionType.WRITE_ONLY); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/BackwardsCompatibleThreePhaseCommitCohort.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/BackwardsCompatibleThreePhaseCommitCohort.java index 30ab97ceb1..f05ef91fc5 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/BackwardsCompatibleThreePhaseCommitCohort.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/compat/BackwardsCompatibleThreePhaseCommitCohort.java @@ -7,17 +7,17 @@ */ package org.opendaylight.controller.cluster.datastore.compat; +import akka.actor.PoisonPill; +import akka.actor.Props; +import akka.japi.Creator; import org.opendaylight.controller.cluster.common.actor.AbstractUntypedActor; import org.opendaylight.controller.cluster.datastore.messages.AbortTransaction; import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransaction; import org.opendaylight.controller.cluster.datastore.messages.CommitTransaction; import org.opendaylight.controller.cluster.datastore.messages.PreCommitTransaction; import org.opendaylight.controller.cluster.datastore.messages.PreCommitTransactionReply; -import akka.actor.PoisonPill; -import akka.actor.Props; -import akka.event.Logging; -import akka.event.LoggingAdapter; -import akka.japi.Creator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An actor to maintain backwards compatibility for the base Helium version where the 3-phase commit @@ -28,7 +28,7 @@ import akka.japi.Creator; */ public class BackwardsCompatibleThreePhaseCommitCohort extends AbstractUntypedActor { - private final LoggingAdapter LOG = Logging.getLogger(getContext().system(), this); + private static final Logger LOG = LoggerFactory.getLogger(BackwardsCompatibleThreePhaseCommitCohort.class); private final String transactionId; diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/identifiers/ShardIdentifier.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/identifiers/ShardIdentifier.java index 5053d47f84..03bae2d99d 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/identifiers/ShardIdentifier.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/identifiers/ShardIdentifier.java @@ -20,6 +20,7 @@ public class ShardIdentifier { private final String shardName; private final String memberName; private final String type; + private final String fullName; public ShardIdentifier(String shardName, String memberName, String type) { @@ -30,6 +31,9 @@ public class ShardIdentifier { this.shardName = shardName; this.memberName = memberName; this.type = type; + + fullName = new StringBuilder(memberName).append("-shard-").append(shardName).append("-") + .append(type).toString(); } @Override @@ -64,14 +68,10 @@ public class ShardIdentifier { return result; } - @Override public String toString() { + @Override + public String toString() { //ensure the output of toString matches the pattern above - return new StringBuilder(memberName) - .append("-shard-") - .append(shardName) - .append("-") - .append(type) - .toString(); + return fullName; } public static Builder builder(){ diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java index c9fdf38931..cb06c898fd 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/ActorContext.java @@ -18,9 +18,13 @@ import akka.actor.PoisonPill; import akka.dispatch.Mapper; import akka.pattern.AskTimeoutException; import akka.util.Timeout; +import com.codahale.metrics.JmxReporter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import com.google.common.util.concurrent.RateLimiter; import java.util.concurrent.TimeUnit; import org.opendaylight.controller.cluster.common.actor.CommonConfig; import org.opendaylight.controller.cluster.datastore.ClusterWrapper; @@ -54,11 +58,11 @@ import scala.concurrent.duration.FiniteDuration; * but should not be passed to actors especially remote actors */ public class ActorContext { - private static final Logger - LOG = LoggerFactory.getLogger(ActorContext.class); - - public static final String MAILBOX = "bounded-mailbox"; - + private static final Logger LOG = LoggerFactory.getLogger(ActorContext.class); + private static final String UNKNOWN_DATA_STORE_TYPE = "unknown"; + private static final String DISTRIBUTED_DATA_STORE_METRIC_REGISTRY = "distributed-data-store"; + private static final String METRIC_RATE = "rate"; + private static final String DOMAIN = "org.opendaylight.controller.cluster.datastore"; private static final Mapper FIND_PRIMARY_FAILURE_TRANSFORMER = new Mapper() { @Override @@ -74,17 +78,23 @@ public class ActorContext { return actualFailure; } }; + public static final String MAILBOX = "bounded-mailbox"; private final ActorSystem actorSystem; private final ActorRef shardManager; private final ClusterWrapper clusterWrapper; private final Configuration configuration; private final DatastoreContext datastoreContext; - private volatile SchemaContext schemaContext; private final FiniteDuration operationDuration; private final Timeout operationTimeout; private final String selfAddressHostPort; + private final RateLimiter txRateLimiter; + private final MetricRegistry metricRegistry = new MetricRegistry(); + private final JmxReporter jmxReporter = JmxReporter.forRegistry(metricRegistry).inDomain(DOMAIN).build(); private final int transactionOutstandingOperationLimit; + private final Timeout transactionCommitOperationTimeout; + + private volatile SchemaContext schemaContext; public ActorContext(ActorSystem actorSystem, ActorRef shardManager, ClusterWrapper clusterWrapper, Configuration configuration) { @@ -100,10 +110,13 @@ public class ActorContext { this.clusterWrapper = clusterWrapper; this.configuration = configuration; this.datastoreContext = datastoreContext; + this.txRateLimiter = RateLimiter.create(datastoreContext.getTransactionCreationInitialRateLimit()); - operationDuration = Duration.create(datastoreContext.getOperationTimeoutInSeconds(), - TimeUnit.SECONDS); + operationDuration = Duration.create(datastoreContext.getOperationTimeoutInSeconds(), TimeUnit.SECONDS); operationTimeout = new Timeout(operationDuration); + transactionCommitOperationTimeout = new Timeout(Duration.create(getDatastoreContext().getShardTransactionCommitTimeoutInSeconds(), + TimeUnit.SECONDS)); + Address selfAddress = clusterWrapper.getSelfAddress(); if (selfAddress != null && !selfAddress.host().isEmpty()) { @@ -113,6 +126,7 @@ public class ActorContext { } transactionOutstandingOperationLimit = new CommonConfig(this.getActorSystem().settings().config()).getMailBoxCapacity(); + jmxReporter.start(); } public DatastoreContext getDatastoreContext() { @@ -446,4 +460,59 @@ public class ActorContext { public int getTransactionOutstandingOperationLimit(){ return transactionOutstandingOperationLimit; } + + /** + * This is a utility method that lets us get a Timer object for any operation. This is a little open-ended to allow + * us to create a timer for pretty much anything. + * + * @param operationName + * @return + */ + public Timer getOperationTimer(String operationName){ + final String rate = MetricRegistry.name(DISTRIBUTED_DATA_STORE_METRIC_REGISTRY, datastoreContext.getDataStoreType(), operationName, METRIC_RATE); + return metricRegistry.timer(rate); + } + + /** + * Get the type of the data store to which this ActorContext belongs + * + * @return + */ + public String getDataStoreType() { + return datastoreContext.getDataStoreType(); + } + + /** + * Set the number of transaction creation permits that are to be allowed + * + * @param permitsPerSecond + */ + public void setTxCreationLimit(double permitsPerSecond){ + txRateLimiter.setRate(permitsPerSecond); + } + + /** + * Get the current transaction creation rate limit + * @return + */ + public double getTxCreationLimit(){ + return txRateLimiter.getRate(); + } + + /** + * Try to acquire a transaction creation permit. Will block if no permits are available. + */ + public void acquireTxCreationPermit(){ + txRateLimiter.acquire(); + } + + /** + * Return the operation timeout to be used when committing transactions + * @return + */ + public Timeout getTransactionCommitOperationTimeout(){ + return transactionCommitOperationTimeout; + } + + } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/SerializationUtils.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/SerializationUtils.java index 5854932a6f..bf9f8d803a 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/SerializationUtils.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/utils/SerializationUtils.java @@ -17,6 +17,7 @@ import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import org.opendaylight.controller.cluster.datastore.node.NormalizedNodeToNodeCodec; +import org.opendaylight.controller.cluster.datastore.node.utils.stream.InvalidNormalizedNodeStreamException; import org.opendaylight.controller.cluster.datastore.node.utils.stream.NormalizedNodeInputStreamReader; import org.opendaylight.controller.cluster.datastore.node.utils.stream.NormalizedNodeOutputStreamWriter; import org.opendaylight.controller.protobuff.messages.common.NormalizedNodeMessages; @@ -93,15 +94,19 @@ public final class SerializationUtils { } public static NormalizedNode deserializeNormalizedNode(DataInput in) { - try { - boolean present = in.readBoolean(); - if(present) { - NormalizedNodeInputStreamReader streamReader = streamReader(in); - return streamReader.readNormalizedNode(); - } - } catch (IOException e) { - throw new IllegalArgumentException("Error deserializing NormalizedNode", e); - } + try { + return tryDeserializeNormalizedNode(in); + } catch (IOException e) { + throw new IllegalArgumentException("Error deserializing NormalizedNode", e); + } + } + + private static NormalizedNode tryDeserializeNormalizedNode(DataInput in) throws IOException { + boolean present = in.readBoolean(); + if(present) { + NormalizedNodeInputStreamReader streamReader = streamReader(in); + return streamReader.readNormalizedNode(); + } return null; } @@ -109,18 +114,17 @@ public final class SerializationUtils { public static NormalizedNode deserializeNormalizedNode(byte [] bytes) { NormalizedNode node = null; try { - node = deserializeNormalizedNode(new DataInputStream(new ByteArrayInputStream(bytes))); - } catch(Exception e) { - } - - if(node == null) { - // Must be from legacy protobuf serialization - try that. + node = tryDeserializeNormalizedNode(new DataInputStream(new ByteArrayInputStream(bytes))); + } catch(InvalidNormalizedNodeStreamException e) { + // Probably from legacy protobuf serialization - try that. try { NormalizedNodeMessages.Node serializedNode = NormalizedNodeMessages.Node.parseFrom(bytes); node = new NormalizedNodeToNodeCodec(null).decode(serializedNode); - } catch (InvalidProtocolBufferException e) { + } catch (InvalidProtocolBufferException e2) { throw new IllegalArgumentException("Error deserializing NormalizedNode", e); } + } catch (IOException e) { + throw new IllegalArgumentException("Error deserializing NormalizedNode", e); } return node; diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/config/yang/config/distributed_datastore_provider/DistributedConfigDataStoreProviderModule.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/config/yang/config/distributed_datastore_provider/DistributedConfigDataStoreProviderModule.java index 711c6a37b5..7e8307465b 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/config/yang/config/distributed_datastore_provider/DistributedConfigDataStoreProviderModule.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/config/yang/config/distributed_datastore_provider/DistributedConfigDataStoreProviderModule.java @@ -41,7 +41,7 @@ public class DistributedConfigDataStoreProviderModule extends } DatastoreContext datastoreContext = DatastoreContext.newBuilder() - .dataStoreMXBeanType("DistributedConfigDatastore") + .dataStoreType("config") .dataStoreProperties(InMemoryDOMDataStoreConfigProperties.create( props.getMaxShardDataChangeExecutorPoolSize().getValue().intValue(), props.getMaxShardDataChangeExecutorQueueSize().getValue().intValue(), @@ -67,9 +67,10 @@ public class DistributedConfigDataStoreProviderModule extends .shardIsolatedLeaderCheckIntervalInMillis( props.getShardIsolatedLeaderCheckIntervalInMillis().getValue()) .shardElectionTimeoutFactor(props.getShardElectionTimeoutFactor().getValue()) + .transactionCreationInitialRateLimit(props.getTxCreationInitialRateLimit().getValue()) .build(); - return DistributedDataStoreFactory.createInstance("config", getConfigSchemaServiceDependency(), + return DistributedDataStoreFactory.createInstance(getConfigSchemaServiceDependency(), datastoreContext, bundleContext); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/config/yang/config/distributed_datastore_provider/DistributedOperationalDataStoreProviderModule.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/config/yang/config/distributed_datastore_provider/DistributedOperationalDataStoreProviderModule.java index d9df06df1c..0655468531 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/config/yang/config/distributed_datastore_provider/DistributedOperationalDataStoreProviderModule.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/config/yang/config/distributed_datastore_provider/DistributedOperationalDataStoreProviderModule.java @@ -41,7 +41,7 @@ public class DistributedOperationalDataStoreProviderModule extends } DatastoreContext datastoreContext = DatastoreContext.newBuilder() - .dataStoreMXBeanType("DistributedOperationalDatastore") + .dataStoreType("operational") .dataStoreProperties(InMemoryDOMDataStoreConfigProperties.create( props.getMaxShardDataChangeExecutorPoolSize().getValue().intValue(), props.getMaxShardDataChangeExecutorQueueSize().getValue().intValue(), @@ -67,10 +67,11 @@ public class DistributedOperationalDataStoreProviderModule extends .shardIsolatedLeaderCheckIntervalInMillis( props.getShardIsolatedLeaderCheckIntervalInMillis().getValue()) .shardElectionTimeoutFactor(props.getShardElectionTimeoutFactor().getValue()) + .transactionCreationInitialRateLimit(props.getTxCreationInitialRateLimit().getValue()) .build(); - return DistributedDataStoreFactory.createInstance("operational", - getOperationalSchemaServiceDependency(), datastoreContext, bundleContext); + return DistributedDataStoreFactory.createInstance(getOperationalSchemaServiceDependency(), + datastoreContext, bundleContext); } public void setBundleContext(BundleContext bundleContext) { diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/yang/distributed-datastore-provider.yang b/opendaylight/md-sal/sal-distributed-datastore/src/main/yang/distributed-datastore-provider.yang index 46cd50d0c1..e2ee7373d0 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/yang/distributed-datastore-provider.yang +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/yang/distributed-datastore-provider.yang @@ -180,6 +180,14 @@ module distributed-datastore-provider { description "The interval at which the leader of the shard will check if its majority followers are active and term itself as isolated"; } + + leaf tx-creation-initial-rate-limit { + default 100; + type non-zero-uint32-type; + description "The initial number of transactions per second that are allowed before the data store + should begin applying back pressure. This number is only used as an initial guidance, + subsequently the datastore measures the latency for a commit and auto-adjusts the rate limit"; + } } // Augments the 'configuration' choice node under modules/module. diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/CompositeModificationByteStringPayloadTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/CompositeModificationByteStringPayloadTest.java index 5b7002eda2..ce7d6303ad 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/CompositeModificationByteStringPayloadTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/CompositeModificationByteStringPayloadTest.java @@ -8,8 +8,6 @@ package org.opendaylight.controller.cluster.datastore; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.assertTrue; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.SerializationUtils; @@ -24,6 +22,9 @@ import org.opendaylight.controller.md.cluster.datastore.model.TestModel; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + @Deprecated public class CompositeModificationByteStringPayloadTest { @@ -69,6 +70,6 @@ public class CompositeModificationByteStringPayloadTest { entries.add(new ReplicatedLogImplEntry(0, 1, payload)); - assertNotNull(new AppendEntries(10, "foobar", 10, 10, entries, 10).toSerializable()); + assertNotNull(new AppendEntries(10, "foobar", 10, 10, entries, 10, -1).toSerializable()); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/CompositeModificationPayloadTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/CompositeModificationPayloadTest.java index a55f6b865d..90b978821f 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/CompositeModificationPayloadTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/CompositeModificationPayloadTest.java @@ -55,7 +55,7 @@ public class CompositeModificationPayloadTest { }); AppendEntries appendEntries = - new AppendEntries(1, "member-1", 0, 100, entries, 1); + new AppendEntries(1, "member-1", 0, 100, entries, 1, -1); AppendEntriesMessages.AppendEntries o = (AppendEntriesMessages.AppendEntries) appendEntries.toSerializable(RaftVersions.HELIUM_VERSION); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxyTest.java index 6f8035e2d1..58aec30a84 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DataChangeListenerRegistrationProxyTest.java @@ -7,7 +7,11 @@ */ package org.opendaylight.controller.cluster.datastore; -import java.util.concurrent.TimeUnit; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.actor.Props; @@ -16,6 +20,9 @@ import akka.dispatch.ExecutionContexts; import akka.dispatch.Futures; import akka.testkit.JavaTestKit; import akka.util.Timeout; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.Uninterruptibles; +import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -36,16 +43,9 @@ import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataCh import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.Uninterruptibles; import scala.concurrent.ExecutionContextExecutor; import scala.concurrent.Future; import scala.concurrent.duration.FiniteDuration; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.eq; /** * Unit tests for DataChangeListenerRegistrationProxy. @@ -207,6 +207,7 @@ public class DataChangeListenerRegistrationProxyTest extends AbstractActorTest { doReturn(Futures.failed(new RuntimeException("mock"))). when(actorContext).executeOperationAsync(any(ActorRef.class), any(Object.class), any(Timeout.class)); + doReturn(mock(DatastoreContext.class)).when(actorContext).getDatastoreContext(); proxy.init(YangInstanceIdentifier.of(TestModel.TEST_QNAME), AsyncDataBroker.DataChangeScope.ONE); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DatastoreContextTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DatastoreContextTest.java new file mode 100644 index 0000000000..d3a3a8fc2d --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DatastoreContextTest.java @@ -0,0 +1,37 @@ +package org.opendaylight.controller.cluster.datastore; + +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; + +public class DatastoreContextTest { + + private DatastoreContext.Builder builder; + + @Before + public void setUp(){ + builder = new DatastoreContext.Builder(); + } + + @Test + public void testDefaults(){ + DatastoreContext build = builder.build(); + + assertEquals(DatastoreContext.DEFAULT_SHARD_TRANSACTION_IDLE_TIMEOUT , build.getShardTransactionIdleTimeout()); + assertEquals(DatastoreContext.DEFAULT_OPERATION_TIMEOUT_IN_SECONDS, build.getOperationTimeoutInSeconds()); + assertEquals(DatastoreContext.DEFAULT_SHARD_TX_COMMIT_TIMEOUT_IN_SECONDS, build.getShardTransactionCommitTimeoutInSeconds()); + assertEquals(DatastoreContext.DEFAULT_JOURNAL_RECOVERY_BATCH_SIZE, build.getShardRaftConfig().getJournalRecoveryLogBatchSize()); + assertEquals(DatastoreContext.DEFAULT_SNAPSHOT_BATCH_COUNT, build.getShardRaftConfig().getSnapshotBatchCount()); + assertEquals(DatastoreContext.DEFAULT_HEARTBEAT_INTERVAL_IN_MILLIS, build.getShardRaftConfig().getHeartBeatInterval().length()); + assertEquals(DatastoreContext.DEFAULT_SHARD_TX_COMMIT_QUEUE_CAPACITY, build.getShardTransactionCommitQueueCapacity()); + assertEquals(DatastoreContext.DEFAULT_SHARD_INITIALIZATION_TIMEOUT, build.getShardInitializationTimeout()); + assertEquals(DatastoreContext.DEFAULT_SHARD_LEADER_ELECTION_TIMEOUT, build.getShardLeaderElectionTimeout()); + assertEquals(DatastoreContext.DEFAULT_PERSISTENT, build.isPersistent()); + assertEquals(DatastoreContext.DEFAULT_CONFIGURATION_READER, build.getConfigurationReader()); + assertEquals(DatastoreContext.DEFAULT_ISOLATED_LEADER_CHECK_INTERVAL_IN_MILLIS, build.getShardRaftConfig().getIsolatedCheckIntervalInMillis()); + assertEquals(DatastoreContext.DEFAULT_SHARD_SNAPSHOT_DATA_THRESHOLD_PERCENTAGE, build.getShardRaftConfig().getSnapshotDataThresholdPercentage()); + assertEquals(DatastoreContext.DEFAULT_SHARD_ELECTION_TIMEOUT_FACTOR, build.getShardRaftConfig().getElectionTimeoutFactor()); + assertEquals(DatastoreContext.DEFAULT_TX_CREATION_INITIAL_RATE_LIMIT, build.getTransactionCreationInitialRateLimit()); + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreIntegrationTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreIntegrationTest.java index 9f5aded352..1ad2be7af1 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreIntegrationTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreIntegrationTest.java @@ -790,8 +790,11 @@ public class DistributedDataStoreIntegrationTest extends AbstractActorTest { Configuration config = new ConfigurationImpl("module-shards.conf", "modules.conf"); ShardStrategyFactory.setConfiguration(config); + datastoreContextBuilder.dataStoreType(typeName); + DatastoreContext datastoreContext = datastoreContextBuilder.build(); - DistributedDataStore dataStore = new DistributedDataStore(getSystem(), typeName, cluster, + + DistributedDataStore dataStore = new DistributedDataStore(getSystem(), cluster, config, datastoreContext); SchemaContext schemaContext = SchemaContextHelper.full(); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreTest.java new file mode 100644 index 0000000000..66fa876277 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreTest.java @@ -0,0 +1,60 @@ +package org.opendaylight.controller.cluster.datastore; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.cluster.datastore.utils.ActorContext; +import org.opendaylight.controller.md.cluster.datastore.model.TestModel; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class DistributedDataStoreTest extends AbstractActorTest { + + private SchemaContext schemaContext; + + @Mock + private ActorContext actorContext; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + schemaContext = TestModel.createTestContext(); + + doReturn(schemaContext).when(actorContext).getSchemaContext(); + } + + @Test + public void testRateLimitingUsedInReadWriteTxCreation(){ + DistributedDataStore distributedDataStore = new DistributedDataStore(actorContext); + + distributedDataStore.newReadWriteTransaction(); + + verify(actorContext, times(1)).acquireTxCreationPermit(); + } + + @Test + public void testRateLimitingUsedInWriteOnlyTxCreation(){ + DistributedDataStore distributedDataStore = new DistributedDataStore(actorContext); + + distributedDataStore.newWriteOnlyTransaction(); + + verify(actorContext, times(1)).acquireTxCreationPermit(); + } + + + @Test + public void testRateLimitingNotUsedInReadOnlyTxCreation(){ + DistributedDataStore distributedDataStore = new DistributedDataStore(actorContext); + + distributedDataStore.newReadOnlyTransaction(); + distributedDataStore.newReadOnlyTransaction(); + distributedDataStore.newReadOnlyTransaction(); + + verify(actorContext, times(0)).acquireTxCreationPermit(); + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardManagerTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardManagerTest.java index 8c56efd413..596761ddc8 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardManagerTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardManagerTest.java @@ -1,5 +1,10 @@ package org.opendaylight.controller.cluster.datastore; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import akka.actor.ActorRef; import akka.actor.Props; import akka.japi.Creator; @@ -11,6 +16,13 @@ import akka.util.Timeout; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.util.concurrent.Uninterruptibles; +import java.net.URI; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -35,20 +47,6 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; import scala.concurrent.Await; import scala.concurrent.Future; -import java.net.URI; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - public class ShardManagerTest extends AbstractActorTest { private static int ID_COUNTER = 1; @@ -73,8 +71,10 @@ public class ShardManagerTest extends AbstractActorTest { } private Props newShardMgrProps() { - return ShardManager.props(shardMrgIDSuffix, new MockClusterWrapper(), new MockConfiguration(), - DatastoreContext.newBuilder().build()); + + DatastoreContext.Builder builder = DatastoreContext.newBuilder(); + builder.dataStoreType(shardMrgIDSuffix); + return ShardManager.props(new MockClusterWrapper(), new MockConfiguration(), builder.build()); } @Test @@ -351,10 +351,8 @@ public class ShardManagerTest extends AbstractActorTest { public void testRecoveryApplicable(){ new JavaTestKit(getSystem()) { { - final Props persistentProps = ShardManager.props(shardMrgIDSuffix, - new MockClusterWrapper(), - new MockConfiguration(), - DatastoreContext.newBuilder().persistent(true).build()); + final Props persistentProps = ShardManager.props(new MockClusterWrapper(), new MockConfiguration(), + DatastoreContext.newBuilder().persistent(true).dataStoreType(shardMrgIDSuffix).build()); final TestActorRef persistentShardManager = TestActorRef.create(getSystem(), persistentProps); @@ -362,10 +360,8 @@ public class ShardManagerTest extends AbstractActorTest { assertTrue("Recovery Applicable", dataPersistenceProvider1.isRecoveryApplicable()); - final Props nonPersistentProps = ShardManager.props(shardMrgIDSuffix, - new MockClusterWrapper(), - new MockConfiguration(), - DatastoreContext.newBuilder().persistent(false).build()); + final Props nonPersistentProps = ShardManager.props(new MockClusterWrapper(), new MockConfiguration(), + DatastoreContext.newBuilder().persistent(false).dataStoreType(shardMrgIDSuffix).build()); final TestActorRef nonPersistentShardManager = TestActorRef.create(getSystem(), nonPersistentProps); @@ -386,7 +382,8 @@ public class ShardManagerTest extends AbstractActorTest { private static final long serialVersionUID = 1L; @Override public ShardManager create() throws Exception { - return new ShardManager(shardMrgIDSuffix, new MockClusterWrapper(), new MockConfiguration(), DatastoreContext.newBuilder().build()) { + return new ShardManager(new MockClusterWrapper(), new MockConfiguration(), + DatastoreContext.newBuilder().dataStoreType(shardMrgIDSuffix).build()) { @Override protected DataPersistenceProvider createDataPersistenceProvider(boolean persistent) { DataPersistenceProviderMonitor dataPersistenceProviderMonitor @@ -426,8 +423,8 @@ public class ShardManagerTest extends AbstractActorTest { private final CountDownLatch recoveryComplete = new CountDownLatch(1); TestShardManager(String shardMrgIDSuffix) { - super(shardMrgIDSuffix, new MockClusterWrapper(), new MockConfiguration(), - DatastoreContext.newBuilder().build()); + super(new MockClusterWrapper(), new MockConfiguration(), + DatastoreContext.newBuilder().dataStoreType(shardMrgIDSuffix).build()); } @Override diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java index 94b9698abf..4b0651a48e 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTest.java @@ -1467,7 +1467,7 @@ public class ShardTest extends AbstractActorTest { NormalizedNode expectedRoot = readStore(shard, YangInstanceIdentifier.builder().build()); - CaptureSnapshot capture = new CaptureSnapshot(-1, -1, -1, -1); + CaptureSnapshot capture = new CaptureSnapshot(-1, -1, -1, -1, -1, -1); shard.tell(capture, getRef()); assertEquals("Snapshot saved", true, latch.get().await(5, TimeUnit.SECONDS)); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java index 69dd706f37..851fb0114b 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ShardTransactionTest.java @@ -412,10 +412,10 @@ public class ShardTransactionTest extends AbstractActorTest { } @Test - public void testOnReceiveCloseTransaction() throws Exception { + public void testReadWriteTxOnReceiveCloseTransaction() throws Exception { new JavaTestKit(getSystem()) {{ final ActorRef transaction = newTransactionActor(store.newReadWriteTransaction(), - "testCloseTransaction"); + "testReadWriteTxOnReceiveCloseTransaction"); watch(transaction); @@ -426,6 +426,35 @@ public class ShardTransactionTest extends AbstractActorTest { }}; } + @Test + public void testWriteOnlyTxOnReceiveCloseTransaction() throws Exception { + new JavaTestKit(getSystem()) {{ + final ActorRef transaction = newTransactionActor(store.newWriteOnlyTransaction(), + "testWriteTxOnReceiveCloseTransaction"); + + watch(transaction); + + transaction.tell(new CloseTransaction().toSerializable(), getRef()); + + expectMsgClass(duration("3 seconds"), CloseTransactionReply.SERIALIZABLE_CLASS); + expectTerminated(duration("3 seconds"), transaction); + }}; + } + + @Test + public void testReadOnlyTxOnReceiveCloseTransaction() throws Exception { + new JavaTestKit(getSystem()) {{ + final ActorRef transaction = newTransactionActor(store.newReadOnlyTransaction(), + "testReadOnlyTxOnReceiveCloseTransaction"); + + watch(transaction); + + transaction.tell(new CloseTransaction().toSerializable(), getRef()); + + expectMsgClass(duration("3 seconds"), Terminated.class); + }}; + } + @Test(expected=UnknownMessageException.class) public void testNegativePerformingWriteOperationOnReadTransaction() throws Exception { final ActorRef shard = createShard(); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ThreePhaseCommitCohortProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ThreePhaseCommitCohortProxyTest.java index 75c93dd5d2..b013515f25 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ThreePhaseCommitCohortProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ThreePhaseCommitCohortProxyTest.java @@ -3,14 +3,20 @@ package org.opendaylight.controller.cluster.datastore; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.isA; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import akka.actor.ActorPath; import akka.actor.ActorSelection; import akka.actor.Props; import akka.dispatch.Futures; +import akka.util.Timeout; +import com.codahale.metrics.Snapshot; +import com.codahale.metrics.Timer; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import java.util.List; @@ -43,11 +49,30 @@ public class ThreePhaseCommitCohortProxyTest extends AbstractActorTest { @Mock private ActorContext actorContext; + @Mock + private DatastoreContext datastoreContext; + + @Mock + private Timer commitTimer; + + @Mock + private Timer.Context commitTimerContext; + + @Mock + private Snapshot commitSnapshot; + @Before public void setUp() { MockitoAnnotations.initMocks(this); doReturn(getSystem()).when(actorContext).getActorSystem(); + doReturn(datastoreContext).when(actorContext).getDatastoreContext(); + doReturn(100).when(datastoreContext).getShardTransactionCommitTimeoutInSeconds(); + doReturn(commitTimer).when(actorContext).getOperationTimer("commit"); + doReturn(commitTimerContext).when(commitTimer).time(); + doReturn(commitSnapshot).when(commitTimer).getSnapshot(); + doReturn(TimeUnit.MILLISECONDS.toNanos(2000) * 1.0).when(commitSnapshot).get95thPercentile(); + doReturn(10.0).when(actorContext).getTxCreationLimit(); } private Future newCohort() { @@ -86,12 +111,12 @@ public class ThreePhaseCommitCohortProxyTest extends AbstractActorTest { } stubber.when(actorContext).executeOperationAsync(any(ActorSelection.class), - isA(requestType)); + isA(requestType), any(Timeout.class)); } private void verifyCohortInvocations(int nCohorts, Class requestType) { verify(actorContext, times(nCohorts)).executeOperationAsync( - any(ActorSelection.class), isA(requestType)); + any(ActorSelection.class), isA(requestType), any(Timeout.class)); } private void propagateExecutionExceptionCause(ListenableFuture future) throws Throwable { @@ -276,8 +301,11 @@ public class ThreePhaseCommitCohortProxyTest extends AbstractActorTest { try { propagateExecutionExceptionCause(proxy.commit()); } finally { + + verify(actorContext, never()).setTxCreationLimit(anyLong()); verifyCohortInvocations(0, CommitTransaction.SERIALIZABLE_CLASS); } + } @Test @@ -294,11 +322,30 @@ public class ThreePhaseCommitCohortProxyTest extends AbstractActorTest { setupMockActorContext(CommitTransaction.SERIALIZABLE_CLASS, new CommitTransactionReply(), new CommitTransactionReply()); + assertEquals(10.0, actorContext.getTxCreationLimit(), 1e-15); + proxy.canCommit().get(5, TimeUnit.SECONDS); proxy.preCommit().get(5, TimeUnit.SECONDS); proxy.commit().get(5, TimeUnit.SECONDS); verifyCohortInvocations(2, CanCommitTransaction.SERIALIZABLE_CLASS); verifyCohortInvocations(2, CommitTransaction.SERIALIZABLE_CLASS); + + // Verify that the creation limit was changed to 0.5 (based on setup) + verify(actorContext, timeout(5000)).setTxCreationLimit(0.5); + } + + @Test + public void testDoNotChangeTxCreationLimitWhenCommittingEmptyTxn() throws Exception { + + ThreePhaseCommitCohortProxy proxy = setupProxy(0); + + assertEquals(10.0, actorContext.getTxCreationLimit(), 1e-15); + + proxy.canCommit().get(5, TimeUnit.SECONDS); + proxy.preCommit().get(5, TimeUnit.SECONDS); + proxy.commit().get(5, TimeUnit.SECONDS); + + verify(actorContext, never()).setTxCreationLimit(anyLong()); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxyTest.java index dd37371a45..23c3a82a38 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionChainProxyTest.java @@ -11,12 +11,15 @@ package org.opendaylight.controller.cluster.datastore; import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.opendaylight.controller.cluster.datastore.utils.ActorContext; import org.opendaylight.controller.cluster.datastore.utils.MockActorContext; import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction; @@ -29,10 +32,17 @@ public class TransactionChainProxyTest extends AbstractActorTest{ ActorContext actorContext = null; SchemaContext schemaContext = mock(SchemaContext.class); + @Mock + ActorContext mockActorContext; + @Before public void setUp() { + MockitoAnnotations.initMocks(this); + actorContext = new MockActorContext(getSystem()); actorContext.setSchemaContext(schemaContext); + + doReturn(schemaContext).when(mockActorContext).getSchemaContext(); } @SuppressWarnings("resource") @@ -76,4 +86,32 @@ public class TransactionChainProxyTest extends AbstractActorTest{ Assert.assertNotEquals(one.getTransactionChainId(), two.getTransactionChainId()); } + + @Test + public void testRateLimitingUsedInReadWriteTxCreation(){ + TransactionChainProxy txChainProxy = new TransactionChainProxy(mockActorContext); + + txChainProxy.newReadWriteTransaction(); + + verify(mockActorContext, times(1)).acquireTxCreationPermit(); + } + + @Test + public void testRateLimitingUsedInWriteOnlyTxCreation(){ + TransactionChainProxy txChainProxy = new TransactionChainProxy(mockActorContext); + + txChainProxy.newWriteOnlyTransaction(); + + verify(mockActorContext, times(1)).acquireTxCreationPermit(); + } + + + @Test + public void testRateLimitingNotUsedInReadOnlyTxCreation(){ + TransactionChainProxy txChainProxy = new TransactionChainProxy(mockActorContext); + + txChainProxy.newReadOnlyTransaction(); + + verify(mockActorContext, times(0)).acquireTxCreationPermit(); + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java index 9d8227a11b..7ce41a4db1 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/TransactionProxyTest.java @@ -1345,15 +1345,15 @@ public class TransactionProxyTest { TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); - long start = System.currentTimeMillis(); + long start = System.nanoTime(); operation.run(transactionProxy); - long end = System.currentTimeMillis(); + long end = System.nanoTime(); Assert.assertTrue(String.format("took less time than expected %s was %s", - mockActorContext.getDatastoreContext().getOperationTimeoutInSeconds()*1000, - (end-start)), (end - start) > mockActorContext.getDatastoreContext().getOperationTimeoutInSeconds()*1000); + TimeUnit.SECONDS.toNanos(mockActorContext.getDatastoreContext().getOperationTimeoutInSeconds()), + (end-start)), (end - start) > TimeUnit.SECONDS.toNanos(mockActorContext.getDatastoreContext().getOperationTimeoutInSeconds())); } @@ -1392,15 +1392,15 @@ public class TransactionProxyTest { TransactionProxy transactionProxy = new TransactionProxy(mockActorContext, READ_WRITE); - long start = System.currentTimeMillis(); + long start = System.nanoTime(); operation.run(transactionProxy); - long end = System.currentTimeMillis(); + long end = System.nanoTime(); Assert.assertTrue(String.format("took more time than expected %s was %s", - mockActorContext.getDatastoreContext().getOperationTimeoutInSeconds()*1000, - (end-start)), (end - start) <= mockActorContext.getDatastoreContext().getOperationTimeoutInSeconds()*1000); + TimeUnit.SECONDS.toNanos(mockActorContext.getDatastoreContext().getOperationTimeoutInSeconds()), + (end-start)), (end - start) <= TimeUnit.SECONDS.toNanos(mockActorContext.getDatastoreContext().getOperationTimeoutInSeconds())); } public void testWriteThrottling(boolean shardFound){ diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/modification/MutableCompositeModificationTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/modification/MutableCompositeModificationTest.java index 8ae2a8657d..b9d44b2586 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/modification/MutableCompositeModificationTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/modification/MutableCompositeModificationTest.java @@ -80,8 +80,7 @@ public class MutableCompositeModificationTest extends AbstractModificationTest { compositeModification.addModification(new WriteModification(writePath, writeData)); } - Stopwatch sw = new Stopwatch(); - sw.start(); + Stopwatch sw = Stopwatch.createStarted(); for(int i = 0; i < 1000; i++) { new ModificationPayload(compositeModification); } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/ActorContextTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/ActorContextTest.java index e4ab969f5c..eae46da2ee 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/ActorContextTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/utils/ActorContextTest.java @@ -2,6 +2,7 @@ package org.opendaylight.controller.cluster.datastore.utils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import akka.actor.ActorRef; import akka.actor.ActorSelection; @@ -12,10 +13,12 @@ import akka.japi.Creator; import akka.testkit.JavaTestKit; import com.google.common.base.Optional; import java.util.concurrent.TimeUnit; +import org.apache.commons.lang.time.StopWatch; import org.junit.Test; import org.opendaylight.controller.cluster.datastore.AbstractActorTest; import org.opendaylight.controller.cluster.datastore.ClusterWrapper; import org.opendaylight.controller.cluster.datastore.Configuration; +import org.opendaylight.controller.cluster.datastore.DatastoreContext; import org.opendaylight.controller.cluster.datastore.messages.FindLocalShard; import org.opendaylight.controller.cluster.datastore.messages.LocalShardFound; import org.opendaylight.controller.cluster.datastore.messages.LocalShardNotFound; @@ -265,4 +268,35 @@ public class ActorContextTest extends AbstractActorTest{ assertEquals(expected, actual); } + @Test + public void testRateLimiting(){ + DatastoreContext mockDataStoreContext = mock(DatastoreContext.class); + + doReturn(155L).when(mockDataStoreContext).getTransactionCreationInitialRateLimit(); + doReturn("config").when(mockDataStoreContext).getDataStoreType(); + + ActorContext actorContext = + new ActorContext(getSystem(), mock(ActorRef.class), mock(ClusterWrapper.class), + mock(Configuration.class), mockDataStoreContext); + + // Check that the initial value is being picked up from DataStoreContext + assertEquals(mockDataStoreContext.getTransactionCreationInitialRateLimit(), actorContext.getTxCreationLimit(), 1e-15); + + actorContext.setTxCreationLimit(1.0); + + assertEquals(1.0, actorContext.getTxCreationLimit(), 1e-15); + + + StopWatch watch = new StopWatch(); + + watch.start(); + + actorContext.acquireTxCreationPermit(); + actorContext.acquireTxCreationPermit(); + actorContext.acquireTxCreationPermit(); + + watch.stop(); + + assertTrue("did not take as much time as expected", watch.getTime() > 1000); + } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/programs/appendentries/Client.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/programs/appendentries/Client.java index 79c1bb4720..28fc6b0f57 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/programs/appendentries/Client.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/programs/appendentries/Client.java @@ -98,7 +98,7 @@ public class Client { } }); - return new AppendEntries(1, "member-1", 0, 100, modification, 1); + return new AppendEntries(1, "member-1", 0, 100, modification, 1, -1); } public static AppendEntries keyValueAppendEntries() { @@ -123,6 +123,6 @@ public class Client { } }); - return new AppendEntries(1, "member-1", 0, 100, modification, 1); + return new AppendEntries(1, "member-1", 0, 100, modification, 1, -1); } } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/resources/simplelogger.properties b/opendaylight/md-sal/sal-distributed-datastore/src/test/resources/simplelogger.properties new file mode 100644 index 0000000000..9ed3d276a3 --- /dev/null +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/resources/simplelogger.properties @@ -0,0 +1,6 @@ +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=hh:mm:ss,S a +org.slf4j.simpleLogger.logFile=System.out +org.slf4j.simpleLogger.showShortLogName=true +org.slf4j.simpleLogger.levelInBrackets=true +org.slf4j.simpleLogger.log.org.opendaylight.controller.cluster.datastore=trace \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dom-api/pom.xml b/opendaylight/md-sal/sal-dom-api/pom.xml index 0302a7d920..89fca50354 100644 --- a/opendaylight/md-sal/sal-dom-api/pom.xml +++ b/opendaylight/md-sal/sal-dom-api/pom.xml @@ -33,6 +33,20 @@ org.osgi org.osgi.core + + + junit + junit + + + org.opendaylight.controller + sal-test-model + + + org.opendaylight.yangtools + yang-data-impl + test + diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeChangeListener.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeChangeListener.java new file mode 100644 index 0000000000..257879070d --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeChangeListener.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import java.util.Collection; +import java.util.EventListener; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; + +/** + * Interface implemented by classes interested in receiving notifications about + * data tree changes. This interface differs from {@link DOMDataChangeListener} + * in that it provides a cursor-based view of the change, which has potentially + * lower overhead. + */ +public interface DOMDataTreeChangeListener extends EventListener { + /** + * Invoked when there was data change for the supplied path, which was used + * to register this listener. + * + *

    + * This method may be also invoked during registration of the listener if + * there is any pre-existing data in the conceptual data tree for supplied + * path. This initial event will contain all pre-existing data as created. + * + *

    + * A data change event may be triggered spuriously, e.g. such that data before + * and after compare as equal. Implementations of this interface are expected + * to recover from such events. Event producers are expected to exert reasonable + * effort to suppress such events. + * + * In other words, it is completely acceptable to observe + * a {@link org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode}, + * which reports a {@link org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType} + * other than UNMODIFIED, while the before- and after- data items compare as + * equal. + * + * @param changes Collection of change events, may not be null or empty. + */ + void onDataTreeChanged(@Nonnull Collection changes); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeChangeService.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeChangeService.java new file mode 100644 index 0000000000..e001dbbf1b --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeChangeService.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.concepts.ListenerRegistration; + +/** + * A {@link DOMService} which allows users to register for changes to a + * subtree. + */ +public interface DOMDataTreeChangeService extends DOMService { + /** + * Registers a {@link DOMDataTreeChangeListener} to receive + * notifications when data changes under a given path in the conceptual data + * tree. + *

    + * You are able to register for notifications for any node or subtree + * which can be represented using {@link DOMDataTreeIdentifier}. + *

    + * + * You are able to register for data change notifications for a subtree or leaf + * even if it does not exist. You will receive notification once that node is + * created. + *

    + * If there is any pre-existing data in the data tree for the path for which you are + * registering, you will receive an initial data change event, which will + * contain all pre-existing data, marked as created. + * + *

    + * This method returns a {@link ListenerRegistration} object. To + * "unregister" your listener for changes call the {@link ListenerRegistration#close()} + * method on the returned object. + *

    + * You MUST explicitly unregister your listener when you no longer want to receive + * notifications. This is especially true in OSGi environments, where failure to + * do so during bundle shutdown can lead to stale listeners being still registered. + * + * @param treeId + * Data tree identifier of the subtree which should be watched for + * changes. + * @param listener + * Listener instance which is being registered + * @return Listener registration object, which may be used to unregister + * your listener using {@link ListenerRegistration#close()} to stop + * delivery of change events. + */ + @Nonnull ListenerRegistration registerDataTreeChangeListener(@Nonnull DOMDataTreeIdentifier treeId, @Nonnull L listener); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeIdentifier.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeIdentifier.java new file mode 100644 index 0000000000..7370ebee7f --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeIdentifier.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import com.google.common.base.Preconditions; +import java.io.Serializable; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.concepts.Path; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +/** + * A unique identifier for a particular subtree. It is composed of the logical + * data store type and the instance identifier of the root node. + */ +public final class DOMDataTreeIdentifier implements Immutable, Path, Serializable { + private static final long serialVersionUID = 1L; + private final YangInstanceIdentifier rootIdentifier; + private final LogicalDatastoreType datastoreType; + + public DOMDataTreeIdentifier(final LogicalDatastoreType datastoreType, final YangInstanceIdentifier rootIdentifier) { + this.datastoreType = Preconditions.checkNotNull(datastoreType); + this.rootIdentifier = Preconditions.checkNotNull(rootIdentifier); + } + + /** + * Return the logical data store type. + * + * @return Logical data store type. Guaranteed to be non-null. + */ + public @Nonnull LogicalDatastoreType getDatastoreType() { + return datastoreType; + } + + /** + * Return the {@link YangInstanceIdentifier} of the root node. + * + * @return Instance identifier corresponding to the root node. + */ + public @Nonnull YangInstanceIdentifier getRootIdentifier() { + return rootIdentifier; + } + + @Override + public boolean contains(final DOMDataTreeIdentifier other) { + return datastoreType == other.datastoreType && rootIdentifier.contains(other.rootIdentifier); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + datastoreType.hashCode(); + result = prime * result + rootIdentifier.hashCode(); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DOMDataTreeIdentifier)) { + return false; + } + DOMDataTreeIdentifier other = (DOMDataTreeIdentifier) obj; + if (datastoreType != other.datastoreType) { + return false; + } + return rootIdentifier.equals(other.rootIdentifier); + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeInaccessibleException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeInaccessibleException.java new file mode 100644 index 0000000000..eea1be51bb --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeInaccessibleException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import com.google.common.base.Preconditions; + + +/** + * Failure reported when a data tree is no longer accessible. + */ +public class DOMDataTreeInaccessibleException extends DOMDataTreeListeningException { + private static final long serialVersionUID = 1L; + private final DOMDataTreeIdentifier treeIdentifier; + + public DOMDataTreeInaccessibleException(final DOMDataTreeIdentifier treeIdentifier, final String message) { + super(message); + this.treeIdentifier = Preconditions.checkNotNull(treeIdentifier); + } + + public DOMDataTreeInaccessibleException(final DOMDataTreeIdentifier treeIdentifier, final String message, final Throwable cause) { + super(message); + this.treeIdentifier = Preconditions.checkNotNull(treeIdentifier); + } + + public final DOMDataTreeIdentifier getTreeIdentifier() { + return treeIdentifier; + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeListener.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeListener.java new file mode 100644 index 0000000000..083cd10ef3 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeListener.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import java.util.Collection; +import java.util.EventListener; +import java.util.Map; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; + +/** + * Interface implemented by data consumers, e.g. processes wanting to act on data + * after it has been introduced to the conceptual data tree. + */ +public interface DOMDataTreeListener extends EventListener { + /** + * Invoked whenever one or more registered subtrees change. The logical changes are reported, + * as well as the roll up of new state for all subscribed subtrees. + * + * @param changes The set of changes being reported. Each subscribed subtree may be present + * at most once. + * @param subtrees Per-subtree state as visible after the reported changes have been applied. + * This includes all the subtrees this listener is subscribed to, even those + * which have not changed. + */ + void onDataTreeChanged(@Nonnull Collection changes, @Nonnull Map> subtrees); + + /** + * Invoked when a subtree listening failure occurs. This can be triggered, for example, when + * a connection to external subtree source is broken. The listener will not receive any other + * callbacks, but its registration still needs to be closed to prevent resource leak. + * + * @param cause Collection of failure causes, may not be null or empty. + */ + void onDataTreeFailed(@Nonnull Collection causes); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeListeningException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeListeningException.java new file mode 100644 index 0000000000..e0d9b427d9 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeListeningException.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +/** + * Base exception for various causes why and {@link DOMDataTreeListener} + * may be terminated by the {@link DOMDataTreeService} implementation. + */ +public class DOMDataTreeListeningException extends Exception { + private static final long serialVersionUID = 1L; + + public DOMDataTreeListeningException(final String message) { + super(message); + } + + public DOMDataTreeListeningException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeLoopException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeLoopException.java new file mode 100644 index 0000000000..8f498a887d --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeLoopException.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import javax.annotation.Nonnull; + +/** + * Exception thrown when a loop is detected in the way {@link DOMDataTreeListener} + * and {@link DOMDataTreeProducer} instances would be connected. + */ +public class DOMDataTreeLoopException extends Exception { + private static final long serialVersionUID = 1L; + + public DOMDataTreeLoopException(final @Nonnull String message) { + super(message); + } + + public DOMDataTreeLoopException(final @Nonnull String message, final @Nonnull Throwable cause) { + super(message, cause); + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducer.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducer.java new file mode 100644 index 0000000000..cbfa0122f2 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducer.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import java.util.Collection; +import javax.annotation.Nonnull; + +/** + * A data producer context. It allows transactions to be submitted to the subtrees + * specified at instantiation time. At any given time there may be a single transaction + * open. It needs to be either submitted or cancelled before another one can be open. + * Once a transaction is submitted, it will proceed to be committed asynchronously. + * + * Each instance has an upper bound on the number of transactions which can be in-flight, + * once that capacity is exceeded, an attempt to create a new transaction will block + * until some transactions complete. + * + * Each {@link DOMDataTreeProducer} can be in two logical states, bound and unbound, + * which define the lifecycle rules for when is it legal to create and submit transactions + * in relationship with {@link DOMDataTreeListener} callbacks. + * + * When a producer is first created, it is unbound. In this state the producer can be + * accessed by any application thread to allocate or submit transactions, as long as + * the 'single open transaction' rule is maintained. The producer and any transaction + * object MUST NOT be accessed, directly or indirectly, from a {@link DOMDataTreeListener} + * callback. + * + * When a producer is referenced in a call to {@link DOMDataTreeService#registerListener(DOMDataTreeListener, java.util.Collection, boolean, java.util.Collection)}, + * an attempt will be made to bind the producer to the specified {@link DOMDataTreeListener}. + * Such an attempt will fail the producer is already bound, or it has an open transaction. + * Once bound, the producer can only be accessed from within the {@link DOMDataTreeListener} + * callback on that particular instance. Any transaction which is not submitted by the + * time the callback returns will be implicitly cancelled. A producer becomes unbound + * when the listener it is bound to becomes unregistered. + */ +public interface DOMDataTreeProducer extends DOMDataTreeProducerFactory, AutoCloseable { + /** + * Allocate a new open transaction on this producer. Any and all transactions + * previously allocated must have been either submitted or cancelled by the + * time this method is invoked. + * + * @param barrier Indicates whether this transaction should be a barrier. A barrier + * transaction is processed separately from any preceding transactions. + * Non-barrier transactions may be merged and processed in a batch, + * such that any observers see the modifications contained in them as + * if the modifications were made in a single transaction. + * @return A new {@link DOMDataWriteTransaction} + * @throws {@link IllegalStateException} if a previous transaction was not closed. + * @throws {@link IllegalThreadStateException} if the calling thread context does not + * match the lifecycle rules enforced by the producer state (e.g. bound or unbound). + * This exception is thrown on a best effort basis and programs should not rely + * on it for correct operation. + */ + @Nonnull DOMDataWriteTransaction createTransaction(boolean isolated); + + /** + * {@inheritDoc} + * + * When invoked on a {@link DOMDataTreeProducer}, this method has additional restrictions. + * There may not be an open transaction from this producer. The method needs to be + * invoked in appropriate context, e.g. bound or unbound. + * + * Specified subtrees must be accessible by this producer. Accessible means they are a subset + * of the subtrees specified when the producer is instantiated. The set is further reduced as + * child producers are instantiated -- if you create a producer for /a and then a child for + * /a/b, /a/b is not accessible from the first producer. + * + * Once this method returns successfully, this (parent) producer loses the ability to + * access the specified paths until the resulting (child) producer is shut down. + * + * @throws {@link IllegalStateException} if there is an open transaction + * @throws {@link IllegalArgumentException} if subtrees contains a subtree which is not + * accessible by this producer + * @throws {@link IllegalThreadStateException} if the calling thread context does not + * match the lifecycle rules enforced by the producer state (e.g. bound or unbound). + * This exception is thrown on a best effort basis and programs should not rely + * on it for correct operation. + */ + @Override + @Nonnull DOMDataTreeProducer createProducer(@Nonnull Collection subtrees); + + /** + * {@inheritDoc} + * + * @throws DOMDataTreeProducerBusyException when there is an open transaction. + */ + @Override + void close() throws DOMDataTreeProducerException; +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducerBusyException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducerBusyException.java new file mode 100644 index 0000000000..a83a5ca3d2 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducerBusyException.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +/** + * Exception indicating that the {@link DOMDataTreeProducer} has an open user + * transaction and cannot be closed. + */ +public class DOMDataTreeProducerBusyException extends DOMDataTreeProducerException { + private static final long serialVersionUID = 1L; + + public DOMDataTreeProducerBusyException(final String message) { + super(message); + } + + public DOMDataTreeProducerBusyException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducerException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducerException.java new file mode 100644 index 0000000000..16c26760ff --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducerException.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +/** + * Base exception for all exceptions related to {@link DOMDataTreeProducer}s. + */ +public class DOMDataTreeProducerException extends Exception { + private static final long serialVersionUID = 1L; + + public DOMDataTreeProducerException(final String message) { + super(message); + } + + public DOMDataTreeProducerException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducerFactory.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducerFactory.java new file mode 100644 index 0000000000..89ac8d1e6c --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeProducerFactory.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import java.util.Collection; +import javax.annotation.Nonnull; + +/** + * Base source of {@link DOMDataTreeProducer}s. This interface is usually not used directly, + * but rather through one of its sub-interfaces. + */ +public interface DOMDataTreeProducerFactory { + /** + * Create a producer, which is able to access to a set of trees. + * + * @param subtrees The collection of subtrees the resulting producer should have access to. + * @return A {@link DOMDataTreeProducer} instance. + * @throws {@link IllegalArgumentException} if subtrees is empty. + */ + @Nonnull DOMDataTreeProducer createProducer(@Nonnull Collection subtrees); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeService.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeService.java new file mode 100644 index 0000000000..21ff44c539 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeService.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import java.util.Collection; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.concepts.ListenerRegistration; + +/** + * A {@link DOMService} providing access to the conceptual data tree. Interactions + * with the data tree are split into data producers and consumers (listeners). Each + * of them operate on a set of subtrees, which need to be declared at instantiation time. + * + * Returned instances are not thread-safe and expected to be used by a single thread + * at a time. Furthermore, producers may not be accessed from consumer callbacks + * unless they were specified when the listener is registered. + * + * The service maintains a loop-free topology of producers and consumers. What this means + * is that a consumer is not allowed to access a producer, which affects any of the + * subtrees it is subscribed to. This restriction is in place to ensure the system does + * not go into a feedback loop, where it is impossible to block either a producer or + * a consumer without accumulating excess work in the backlog stemming from its previous + * activity. + */ +public interface DOMDataTreeService extends DOMDataTreeProducerFactory, DOMService { + /** + * Register a {@link DOMDataTreeListener} instance. Once registered, the listener + * will start receiving changes on the selected subtrees. If the listener cannot + * keep up with the rate of changes, and allowRxMerges is set to true, this service + * is free to merge the changes, so that a smaller number of them will be reported, + * possibly hiding some data transitions (like flaps). + * + * If the listener wants to write into any producer, that producer has to be mentioned + * in the call to this method. Those producers will be bound exclusively to the + * registration, so that accessing them outside of this listener's callback will trigger + * an error. Any producers mentioned must be idle, e.g. they may not have an open + * transaction at the time this method is invoked. + * + * Each listener instance can be registered at most once. Implementations of this + * interface have to guarantee that the listener's methods will not be invoked + * concurrently from multiple threads. + * + * @param listener {@link DOMDataTreeListener} that is being registered + * @param subtrees Conceptual subtree identifier of subtrees which should be monitored + * for changes. May not be null or empty. + * @param allowRxMerges True if the backend may perform ingress state compression. + * @param producers {@link DOMDataTreeProducer} instances to bind to the listener. + * @return A listener registration. Once closed, the listener will no longer be + * invoked and the producers will be unbound. + * @throws IllegalArgumentException if subtrees is empty or the listener is already bound + * @throws DOMDataTreeLoopException if the registration of the listener to the specified + * subtrees with specified producers would form a + * feedback loop + */ + @Nonnull ListenerRegistration registerListener(@Nonnull T listener, + @Nonnull Collection subtrees, boolean allowRxMerges, @Nonnull Collection producers); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeShard.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeShard.java new file mode 100644 index 0000000000..e9ef0034ea --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeShard.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import java.util.EventListener; +import javax.annotation.Nonnull; + +/** + * A single shard of the conceptual data tree. This interface defines the basic notifications + * a shard can receive. Each shard implementation is expected to also implement some of the + * datastore-level APIs. Which interfaces are required depends on the {@link DOMDataTreeShardingService} + * implementation. + */ +public interface DOMDataTreeShard extends EventListener { + /** + * Invoked whenever a child is getting attached as a more specific prefix under this shard. + * + * @param prefix Child's prefix + * @param child Child shard + */ + void onChildAttached(@Nonnull DOMDataTreeIdentifier prefix, @Nonnull DOMDataTreeShard child); + + /** + * Invoked whenever a child is getting detached as a more specific prefix under this shard. + * + * @param prefix Child's prefix + * @param child Child shard + */ + void onChildDetached(@Nonnull DOMDataTreeIdentifier prefix, @Nonnull DOMDataTreeShard child); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeShardingConflictException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeShardingConflictException.java new file mode 100644 index 0000000000..d44316d39d --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeShardingConflictException.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import javax.annotation.Nonnull; + +/** + * Exception thrown when an attempt to attach a conflicting shard to the global + * table. + */ +public class DOMDataTreeShardingConflictException extends Exception { + private static final long serialVersionUID = 1L; + + public DOMDataTreeShardingConflictException(final @Nonnull String message) { + super(message); + } + + public DOMDataTreeShardingConflictException(final @Nonnull String message, final @Nonnull Throwable cause) { + super(message, cause); + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeShardingService.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeShardingService.java new file mode 100644 index 0000000000..c087224090 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMDataTreeShardingService.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.concepts.ListenerRegistration; + +/** + * A {@link DOMService} providing access to details on how the conceptual data tree + * is distributed among providers (also known as shards). Each shard is tied to a + * single {@link DOMDataTreeIdentifier}. Based on those data tree identifiers, the + * shards are organized in a tree, where there is a logical parent/child relationship. + * + * It is not allowed to attach two shards to the same data tree identifier, which means + * the mapping of each piece of information has an unambiguous home. When accessing + * the information, the shard with the longest matching data tree identifier is used, + * which is why this interface treats it is a prefix. + * + * Whenever a parent/child relationship is changed, the parent is notified, so it can + * understand that a logical child has been attached. + */ +public interface DOMDataTreeShardingService extends DOMService { + /** + * Register a shard as responsible for a particular subtree prefix. + * + * @param prefix Data tree identifier, may not be null. + * @param shard Responsible shard instance + * @return A registration. To remove the shard's binding, close the registration. + * @throws DOMDataTreeShardingConflictException if the prefix is already bound + */ + @Nonnull ListenerRegistration registerDataTreeShard(@Nonnull DOMDataTreeIdentifier prefix, @Nonnull T shard) throws DOMDataTreeShardingConflictException; +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMNotificationListenerRegistration.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMNotificationListenerRegistration.java deleted file mode 100644 index 4dccad2e42..0000000000 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMNotificationListenerRegistration.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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.md.sal.dom.api; - -import org.opendaylight.yangtools.concepts.ListenerRegistration; - -/** - * A registration of a {@link DOMNotificationListener}. Invoking {@link #close()} will prevent further - * delivery of events to the listener. - */ -public interface DOMNotificationListenerRegistration extends ListenerRegistration { - -} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMNotificationService.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMNotificationService.java index 6bce9c447a..4b9f8ca7fc 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMNotificationService.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMNotificationService.java @@ -9,6 +9,7 @@ package org.opendaylight.controller.md.sal.dom.api; import java.util.Collection; import javax.annotation.Nonnull; +import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.model.api.SchemaPath; /** @@ -30,7 +31,7 @@ public interface DOMNotificationService { * null or a SchemaPath which does not represent a valid {@link DOMNotification} type. * @throws NullPointerException if either of the arguments is null */ - DOMNotificationListenerRegistration registerNotificationListener(@Nonnull DOMNotificationListener listener, @Nonnull Collection types); + ListenerRegistration registerNotificationListener(@Nonnull T listener, @Nonnull Collection types); /** * Register a {@link DOMNotificationListener} to receive a set of notifications. As with @@ -47,5 +48,5 @@ public interface DOMNotificationService { * @throws NullPointerException if listener is null */ // FIXME: Java 8: provide a default implementation of this method. - DOMNotificationListenerRegistration registerNotificationListener(@Nonnull DOMNotificationListener listener, SchemaPath... types); + ListenerRegistration registerNotificationListener(@Nonnull T listener, SchemaPath... types); } diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcAvailabilityListener.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcAvailabilityListener.java new file mode 100644 index 0000000000..77d42a43c9 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcAvailabilityListener.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import java.util.Collection; +import java.util.EventListener; +import javax.annotation.Nonnull; + +/** + * An {@link EventListener} used to track RPC implementations becoming (un)available + * to a {@link DOMRpcService}. + */ +public interface DOMRpcAvailabilityListener extends EventListener { + /** + * Method invoked whenever an RPC type becomes available. + * + * @param rpcs RPC types newly available + */ + void onRpcAvailable(@Nonnull Collection rpcs); + + /** + * Method invoked whenever an RPC type becomes unavailable. + * + * @param rpcs RPC types which became unavailable + */ + void onRpcUnavailable(@Nonnull Collection rpcs); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcException.java new file mode 100644 index 0000000000..7ea4f4cf56 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +/** + * Base class for failures that can occur during RPC invocation. This covers + * transport and protocol-level failures. + */ +public abstract class DOMRpcException extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Construct an new instance with a message and an empty cause. + * + * @param message Exception message + */ + protected DOMRpcException(final String message) { + super(message); + } + + /** + * Construct an new instance with a message and a cause. + * + * @param message Exception message + * @param cause Chained cause + */ + protected DOMRpcException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcIdentifier.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcIdentifier.java new file mode 100644 index 0000000000..4e6c7a5f7f --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcIdentifier.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +/** + * Identifier of a RPC context. This is an extension of the YANG RPC, which + * always has global context. It allows an RPC to have a instance identifier + * attached, so that there can be multiple implementations bound to different + * contexts concurrently. + */ +public abstract class DOMRpcIdentifier { + private static final class Global extends DOMRpcIdentifier { + private Global(final @Nonnull SchemaPath type) { + super(type); + } + + @Override + public YangInstanceIdentifier getContextReference() { + return null; + } + } + + private static final class Local extends DOMRpcIdentifier { + private final YangInstanceIdentifier contextReference; + + private Local(final @Nonnull SchemaPath type, final @Nonnull YangInstanceIdentifier contextReference) { + super(type); + this.contextReference = Preconditions.checkNotNull(contextReference); + } + + @Override + public YangInstanceIdentifier getContextReference() { + return contextReference; + } + } + + private final SchemaPath type; + + private DOMRpcIdentifier(final SchemaPath type) { + this.type = Preconditions.checkNotNull(type); + } + + /** + * Create a global RPC identifier. + * + * @param type RPC type, SchemaPath of its definition, may not be null + * @return A global RPC identifier, guaranteed to be non-null. + */ + public static @Nonnull DOMRpcIdentifier create(final @Nonnull SchemaPath type) { + return new Global(type); + } + + /** + * Create an RPC identifier with a particular context reference. + * + * @param type RPC type, SchemaPath of its definition, may not be null + * @param contextReference Context reference, null means a global RPC identifier. + * @return A global RPC identifier, guaranteed to be non-null. + */ + public static @Nonnull DOMRpcIdentifier create(final @Nonnull SchemaPath type, final @Nullable YangInstanceIdentifier contextReference) { + if (contextReference == null) { + return new Global(type); + } else { + return new Local(type, contextReference); + } + } + + /** + * Return the RPC type. + * + * @return RPC type. + */ + public final @Nonnull SchemaPath getType() { + return type; + } + + /** + * Return the RPC context reference. Null value indicates global context. + * + * @return RPC context reference. + */ + public abstract @Nullable YangInstanceIdentifier getContextReference(); + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + type.hashCode(); + result = prime * result + (getContextReference() == null ? 0 : getContextReference().hashCode()); + return result; + } + + @Override + public final boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DOMRpcIdentifier)) { + return false; + } + DOMRpcIdentifier other = (DOMRpcIdentifier) obj; + if (!type.equals(other.type)) { + return false; + } + return Objects.equals(getContextReference(), other.getContextReference()); + } + + @Override + public final String toString() { + return MoreObjects.toStringHelper(this).omitNullValues().add("type", type).add("contextReference", getContextReference()).toString(); + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementation.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementation.java new file mode 100644 index 0000000000..c246c76270 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementation.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import com.google.common.util.concurrent.CheckedFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * Interface implemented by an individual RPC implementation. This API allows for dispatch + * implementations, e.g. an individual object handling a multitude of RPCs. + */ +public interface DOMRpcImplementation { + /** + * Initiate invocation of the RPC. Implementations of this method are + * expected to not block on external resources. + * + * @param rpc RPC identifier which was invoked + * @param input Input arguments, null if the RPC does not take any. + * @return A {@link CheckedFuture} which will return either a result structure, + * or report a subclass of {@link DOMRpcException} reporting a transport + * error. + */ + @Nonnull CheckedFuture invokeRpc(@Nonnull DOMRpcIdentifier rpc, @Nullable NormalizedNode input); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationNotAvailableException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationNotAvailableException.java new file mode 100644 index 0000000000..cca9a452b8 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationNotAvailableException.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import com.google.common.base.Preconditions; +import javax.annotation.Nonnull; + +/** + * Exception indicating that no implementation of the requested RPC service is available. + */ +public class DOMRpcImplementationNotAvailableException extends DOMRpcException { + private static final long serialVersionUID = 1L; + + public DOMRpcImplementationNotAvailableException(@Nonnull final String format, final Object... args) { + super(String.format(format, args)); + } + + public DOMRpcImplementationNotAvailableException(@Nonnull final Throwable cause, @Nonnull final String format, final Object... args) { + super(String.format(format, args), Preconditions.checkNotNull(cause)); + } +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationRegistration.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationRegistration.java new file mode 100644 index 0000000000..0b8dff50bb --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationRegistration.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import org.opendaylight.yangtools.concepts.ObjectRegistration; + +/** + * A registration of a {@link DOMRpcImplementation}. Used to track and revoke a registration + * with a {@link DOMRpcProviderService}. + * + * @param RPC implementation type + */ +public interface DOMRpcImplementationRegistration extends ObjectRegistration { + @Override + void close(); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcProviderService.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcProviderService.java new file mode 100644 index 0000000000..4a4f9656ba --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcProviderService.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import java.util.Set; +import javax.annotation.Nonnull; + +/** + * A {@link DOMService} which allows registration of RPC implementations with a conceptual + * router. The client counterpart of this service is {@link DOMRpcService}. + */ +public interface DOMRpcProviderService extends DOMService { + /** + * Register an {@link DOMRpcImplementation} object with this service. + * + * @param implementation RPC implementation, must not be null + * @param rpcs Array of supported RPC identifiers. Must not be null, empty, or contain a null element. + * Each identifier is added exactly once, no matter how many times it occurs. + * @return A {@link DOMRpcImplementationRegistration} object, guaranteed to be non-null. + * @throws NullPointerException if implementation or types is null + * @throws IllegalArgumentException if types is empty or contains a null element. + */ + @Nonnull DOMRpcImplementationRegistration registerRpcImplementation(@Nonnull T implementation, @Nonnull DOMRpcIdentifier... rpcs); + + /** + * Register an {@link DOMRpcImplementation} object with this service. + * + * @param implementation RPC implementation, must not be null + * @param rpcs Set of supported RPC identifiers. Must not be null, empty, or contain a null element. + * @return A {@link DOMRpcImplementationRegistration} object, guaranteed to be non-null. + * @throws NullPointerException if implementation or types is null + * @throws IllegalArgumentException if types is empty or contains a null element. + */ + @Nonnull DOMRpcImplementationRegistration registerRpcImplementation(@Nonnull T implementation, @Nonnull Set rpcs); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcResult.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcResult.java new file mode 100644 index 0000000000..5893688870 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcResult.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import java.util.Collection; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * Interface defining a result of an RPC call. + */ +public interface DOMRpcResult { + /** + * Returns a set of errors and warnings which occurred during processing + * the call. + * + * @return a Collection of {@link RpcError}, guaranteed to be non-null. In case + * no errors are reported, an empty collection is returned. + */ + @Nonnull Collection getErrors(); + + /** + * Returns the value result of the call or null if no result is available. + * + * @return Invocation result, null if the operation has not produced a result. This might + * be the case if the operation does not produce a result, or if it failed. + */ + @Nullable NormalizedNode getResult(); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcService.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcService.java new file mode 100644 index 0000000000..48f6ab6500 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcService.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import com.google.common.util.concurrent.CheckedFuture; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +/** + * A {@link DOMService} which allows clients to invoke RPCs. The conceptual model of this + * service is that of a dynamic router, where the set of available RPC services can change + * dynamically. The service allows users to add a listener to track the process of + * RPCs becoming available. + */ +public interface DOMRpcService extends DOMService { + /** + * Initiate invocation of an RPC. This method is guaranteed to not block on any external + * resources. + * + * @param type SchemaPath of the RPC to be invoked + * @param input Input arguments, null if the RPC does not take any. + * @return A {@link CheckedFuture} which will return either a result structure, + * or report a subclass of {@link DOMRpcException} reporting a transport + * error. + */ + @Nonnull CheckedFuture invokeRpc(@Nonnull SchemaPath type, @Nullable NormalizedNode input); + + /** + * Register a {@link DOMRpcAvailabilityListener} with this service to receive notifications + * about RPC implementations becoming (un)available. The listener will be invoked with the + * current implementations reported and will be kept uptodate as implementations come and go. + * + * Users should note that using a listener does not necessarily mean that {@link #invokeRpc(SchemaPath, NormalizedNode)} + * will not report a failure due to {@link DOMRpcImplementationNotAvailableException} and + * need to be ready to handle it. Implementations are encouraged to take reasonable precautions + * to prevent this scenario from occurring. + * + * @param listener {@link DOMRpcAvailabilityListener} instance to register + * @return A {@link DOMRpcAvailabilityListenerRegistration} representing this registration. Performing + * a {@link DOMRpcAvailabilityListenerRegistration#close()} will cancel it. Returned object + * is guaranteed to be non-null. + */ + @Nonnull ListenerRegistration registerRpcListener(@Nonnull T listener); +} diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcConsumptionRegistry.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcConsumptionRegistry.java index da592e9b22..5d086e59a7 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcConsumptionRegistry.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcConsumptionRegistry.java @@ -8,11 +8,14 @@ package org.opendaylight.controller.sal.core.api; import java.util.concurrent.Future; - import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; +/** + * @deprecated Use {@link org.opendaylight.controller.md.sal.dom.api.DOMRpcService} instead. + */ +@Deprecated public interface RpcConsumptionRegistry { /** * Sends an RPC to other components registered to the broker. diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcImplementation.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcImplementation.java index d14910055b..5055ad1430 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcImplementation.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcImplementation.java @@ -7,14 +7,12 @@ */ package org.opendaylight.controller.sal.core.api; +import com.google.common.util.concurrent.ListenableFuture; import java.util.Set; - import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; -import com.google.common.util.concurrent.ListenableFuture; - /** * {@link Provider}'s implementation of an RPC. * @@ -42,7 +40,10 @@ import com.google.common.util.concurrent.ListenableFuture; * {@link RpcResult} *

  • {@link Broker} returns the {@link RpcResult} to {@link Consumer} * + * + * @deprecated Use {@link org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation} instead. */ +@Deprecated public interface RpcImplementation extends Provider.ProviderFunctionality { /** diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcImplementationUnavailableException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcImplementationUnavailableException.java index 371082223a..45f13654d8 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcImplementationUnavailableException.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcImplementationUnavailableException.java @@ -9,7 +9,10 @@ package org.opendaylight.controller.sal.core.api; /** * Exception reported when no RPC implementation is found in the system. + * + * @deprecated Use {@link org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException} instead. */ +@Deprecated public class RpcImplementationUnavailableException extends RuntimeException { private static final long serialVersionUID = 1L; diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcProvisionRegistry.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcProvisionRegistry.java index 050225c5c2..1caed09437 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcProvisionRegistry.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcProvisionRegistry.java @@ -15,6 +15,10 @@ import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +/** + * @deprecated Use {@link org.opendaylight.controller.md.sal.dom.api.DOMRpcProviderService} and {@link org.opendaylight.controller.md.sal.dom.api.DOMRpcService} instead. + */ +@Deprecated public interface RpcProvisionRegistry extends RpcImplementation, BrokerService, RouteChangePublisher, DOMService { /** diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcRegistrationListener.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcRegistrationListener.java index a0be886b24..df20650ac4 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcRegistrationListener.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/RpcRegistrationListener.java @@ -8,9 +8,12 @@ package org.opendaylight.controller.sal.core.api; import java.util.EventListener; - import org.opendaylight.yangtools.yang.common.QName; +/** + * @deprecated Use {@link org.opendaylight.controller.md.sal.dom.api.DOMRpcAvailabilityListener} instead. + */ +@Deprecated public interface RpcRegistrationListener extends EventListener { public void onRpcImplementationAdded(QName name); diff --git a/opendaylight/md-sal/sal-dom-api/src/test/java/org/opendaylight/controller/md/sal/dom/api/AbstractDOMDataTreeServiceTestSuite.java b/opendaylight/md-sal/sal-dom-api/src/test/java/org/opendaylight/controller/md/sal/dom/api/AbstractDOMDataTreeServiceTestSuite.java new file mode 100644 index 0000000000..896c606eb9 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-api/src/test/java/org/opendaylight/controller/md/sal/dom/api/AbstractDOMDataTreeServiceTestSuite.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.api; + +import static org.junit.Assert.assertNotNull; +import com.google.common.util.concurrent.CheckedFuture; +import java.net.URI; +import java.util.Collections; +import javax.annotation.Nonnull; +import org.junit.Test; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; + +/** + * Abstract test suite demonstrating various access patterns on how a {@link DOMDataTreeService} + * can be used. + */ +public abstract class AbstractDOMDataTreeServiceTestSuite { + protected static final QNameModule TEST_MODULE = QNameModule.create(URI.create("urn:opendaylight:params:xml:ns:yang:controller:md:sal:test:store"), null); + + protected static final YangInstanceIdentifier UNORDERED_CONTAINER_IID = YangInstanceIdentifier.create( + new NodeIdentifier(QName.create(TEST_MODULE, "lists")), + new NodeIdentifier(QName.create(TEST_MODULE, "unordered-container"))); + protected static final DOMDataTreeIdentifier UNORDERED_CONTAINER_TREE = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, UNORDERED_CONTAINER_IID); + + /** + * Return a reference to the service used in this test. The instance + * needs to be reused within the same test and must be isolated between + * tests. + * + * @return {@link DOMDataTreeService} instance. + */ + protected abstract @Nonnull DOMDataTreeService service(); + + /** + * A simple unbound producer. It write some basic things into the data store based on the + * test model. + * @throws DOMDataTreeProducerException + * @throws TransactionCommitFailedException + */ + @Test + public final void testBasicProducer() throws DOMDataTreeProducerException, TransactionCommitFailedException { + // Create a producer. It is an AutoCloseable resource, hence the try-with pattern + try (final DOMDataTreeProducer prod = service().createProducer(Collections.singleton(UNORDERED_CONTAINER_TREE))) { + assertNotNull(prod); + + final DOMDataWriteTransaction tx = prod.createTransaction(true); + assertNotNull(tx); + + tx.put(LogicalDatastoreType.OPERATIONAL, UNORDERED_CONTAINER_IID, ImmutableContainerNodeBuilder.create().build()); + + final CheckedFuture f = tx.submit(); + assertNotNull(f); + + f.checkedGet(); + } + } + + // TODO: simple listener +} diff --git a/opendaylight/md-sal/sal-dom-broker/pom.xml b/opendaylight/md-sal/sal-dom-broker/pom.xml index a824792cf8..477ddeabdf 100644 --- a/opendaylight/md-sal/sal-dom-broker/pom.xml +++ b/opendaylight/md-sal/sal-dom-broker/pom.xml @@ -15,8 +15,8 @@ guava - junit - junit + com.lmax + disruptor org.opendaylight.controller @@ -60,6 +60,10 @@ ietf-yang-types + + junit + junit + org.slf4j slf4j-api diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/AbstractDOMRpcRoutingTableEntry.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/AbstractDOMRpcRoutingTableEntry.java new file mode 100644 index 0000000000..09c4f4fe6d --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/AbstractDOMRpcRoutingTableEntry.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.broker.impl; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.util.concurrent.CheckedFuture; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +abstract class AbstractDOMRpcRoutingTableEntry { + private final Map> impls; + private final SchemaPath schemaPath; + + protected AbstractDOMRpcRoutingTableEntry(final SchemaPath schemaPath, final Map> impls) { + this.schemaPath = Preconditions.checkNotNull(schemaPath); + this.impls = Preconditions.checkNotNull(impls); + } + + protected final SchemaPath getSchemaPath() { + return schemaPath; + } + + protected final List getImplementations(final YangInstanceIdentifier context) { + return impls.get(context); + } + + final Map> getImplementations() { + return impls; + } + + public boolean containsContext(final YangInstanceIdentifier contextReference) { + return impls.containsKey(contextReference); + } + + final Set registeredIdentifiers() { + return impls.keySet(); + } + + final AbstractDOMRpcRoutingTableEntry add(final DOMRpcImplementation implementation, final List newRpcs) { + final Builder> vb = ImmutableMap.builder(); + for (Entry> ve : impls.entrySet()) { + if (newRpcs.remove(ve.getKey())) { + final ArrayList i = new ArrayList<>(ve.getValue().size() + 1); + i.addAll(ve.getValue()); + i.add(implementation); + vb.put(ve.getKey(), i); + } else { + vb.put(ve); + } + } + + return newInstance(vb.build()); + } + + final AbstractDOMRpcRoutingTableEntry remove(final DOMRpcImplementation implementation, final List removed) { + final Builder> vb = ImmutableMap.builder(); + for (Entry> ve : impls.entrySet()) { + if (removed.remove(ve.getKey())) { + final ArrayList i = new ArrayList<>(ve.getValue()); + i.remove(implementation); + // We could trimToSize(), but that may perform another copy just to get rid + // of a single element. That is probably not worth the trouble. + if (!i.isEmpty()) { + vb.put(ve.getKey(), i); + } + } else { + vb.put(ve); + } + } + + final Map> v = vb.build(); + return v.isEmpty() ? null : newInstance(v); + } + + protected abstract CheckedFuture invokeRpc(final NormalizedNode input); + protected abstract AbstractDOMRpcRoutingTableEntry newInstance(final Map> impls); +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMForwardedWriteTransaction.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMForwardedWriteTransaction.java index 45e18ff219..bc11f122e6 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMForwardedWriteTransaction.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMForwardedWriteTransaction.java @@ -108,6 +108,7 @@ class DOMForwardedWriteTransaction extends if (impl != null) { LOG.trace("Transaction {} cancelled before submit", getIdentifier()); FUTURE_UPDATER.lazySet(this, CANCELLED_FUTURE); + closeSubtransactions(); return true; } diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMNotificationRouter.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMNotificationRouter.java new file mode 100644 index 0000000000..aac425b3d4 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMNotificationRouter.java @@ -0,0 +1,190 @@ +/* + * 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.md.sal.dom.broker.impl; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableMultimap.Builder; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.lmax.disruptor.EventHandler; +import com.lmax.disruptor.InsufficientCapacityException; +import com.lmax.disruptor.SleepingWaitStrategy; +import com.lmax.disruptor.WaitStrategy; +import com.lmax.disruptor.dsl.Disruptor; +import com.lmax.disruptor.dsl.ProducerType; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.opendaylight.controller.md.sal.dom.api.DOMNotification; +import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener; +import org.opendaylight.controller.md.sal.dom.api.DOMNotificationPublishService; +import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService; +import org.opendaylight.yangtools.concepts.AbstractListenerRegistration; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +/** + * Joint implementation of {@link DOMNotificationPublishService} and {@link DOMNotificationService}. Provides + * routing of notifications from publishers to subscribers. + * + * Internal implementation works by allocating a two-handler Disruptor. The first handler delivers notifications + * to subscribed listeners and the second one notifies whoever may be listening on the returned future. Registration + * state tracking is performed by a simple immutable multimap -- when a registration or unregistration occurs we + * re-generate the entire map from scratch and set it atomically. While registrations/unregistrations synchronize + * on this instance, notifications do not take any locks here. + * + * The fully-blocking {@link #publish(long, DOMNotification, Collection)} and non-blocking {@link #offerNotification(DOMNotification)} + * are realized using the Disruptor's native operations. The bounded-blocking {@link #offerNotification(DOMNotification, long, TimeUnit)} + * is realized by arming a background wakeup interrupt. + */ +public final class DOMNotificationRouter implements AutoCloseable, DOMNotificationPublishService, DOMNotificationService { + private static final ListenableFuture NO_LISTENERS = Futures.immediateFuture(null); + private static final WaitStrategy DEFAULT_STRATEGY = new SleepingWaitStrategy(); + private static final EventHandler DISPATCH_NOTIFICATIONS = new EventHandler() { + @Override + public void onEvent(final DOMNotificationRouterEvent event, final long sequence, final boolean endOfBatch) throws Exception { + event.deliverNotification(); + + } + }; + private static final EventHandler NOTIFY_FUTURE = new EventHandler() { + @Override + public void onEvent(final DOMNotificationRouterEvent event, final long sequence, final boolean endOfBatch) { + event.setFuture(); + } + }; + + private final Disruptor disruptor; + private final ExecutorService executor; + private volatile Multimap> listeners = ImmutableMultimap.of(); + + private DOMNotificationRouter(final ExecutorService executor, final Disruptor disruptor) { + this.executor = Preconditions.checkNotNull(executor); + this.disruptor = Preconditions.checkNotNull(disruptor); + } + + @SuppressWarnings("unchecked") + public static DOMNotificationRouter create(final int queueDepth) { + final ExecutorService executor = Executors.newCachedThreadPool(); + final Disruptor disruptor = new Disruptor<>(DOMNotificationRouterEvent.FACTORY, queueDepth, executor, ProducerType.MULTI, DEFAULT_STRATEGY); + + disruptor.after(DISPATCH_NOTIFICATIONS).handleEventsWith(NOTIFY_FUTURE); + disruptor.start(); + + return new DOMNotificationRouter(executor, disruptor); + } + + @Override + public synchronized ListenerRegistration registerNotificationListener(final T listener, final Collection types) { + final ListenerRegistration reg = new AbstractListenerRegistration(listener) { + @Override + protected void removeRegistration() { + final ListenerRegistration me = this; + + synchronized (DOMNotificationRouter.this) { + listeners = ImmutableMultimap.copyOf(Multimaps.filterValues(listeners, new Predicate>() { + @Override + public boolean apply(final ListenerRegistration input) { + return input != me; + } + })); + } + } + }; + + if (!types.isEmpty()) { + final Builder> b = ImmutableMultimap.builder(); + b.putAll(listeners); + + for (SchemaPath t : types) { + b.put(t, reg); + } + + listeners = b.build(); + } + + return reg; + } + + @Override + public ListenerRegistration registerNotificationListener(final T listener, final SchemaPath... types) { + return registerNotificationListener(listener, Arrays.asList(types)); + } + + private ListenableFuture publish(final long seq, final DOMNotification notification, final Collection> subscribers) { + final DOMNotificationRouterEvent event = disruptor.get(seq); + final ListenableFuture future = event.initialize(notification, subscribers); + disruptor.getRingBuffer().publish(seq); + return future; + } + + @Override + public ListenableFuture putNotification(final DOMNotification notification) throws InterruptedException { + final Collection> subscribers = listeners.get(notification.getType()); + if (subscribers.isEmpty()) { + return NO_LISTENERS; + } + + final long seq = disruptor.getRingBuffer().next(); + return publish(seq, notification, subscribers); + } + + private ListenableFuture tryPublish(final DOMNotification notification, final Collection> subscribers) { + final long seq; + try { + seq = disruptor.getRingBuffer().tryNext(); + } catch (InsufficientCapacityException e) { + return DOMNotificationPublishService.REJECTED; + } + + return publish(seq, notification, subscribers); + } + + @Override + public ListenableFuture offerNotification(final DOMNotification notification) { + final Collection> subscribers = listeners.get(notification.getType()); + if (subscribers.isEmpty()) { + return NO_LISTENERS; + } + + return tryPublish(notification, subscribers); + } + + @Override + public ListenableFuture offerNotification(final DOMNotification notification, final long timeout, + final TimeUnit unit) throws InterruptedException { + final Collection> subscribers = listeners.get(notification.getType()); + if (subscribers.isEmpty()) { + return NO_LISTENERS; + } + + // Attempt to perform a non-blocking publish first + final ListenableFuture noBlock = tryPublish(notification, subscribers); + if (!DOMNotificationPublishService.REJECTED.equals(noBlock)) { + return noBlock; + } + + /* + * FIXME: we need a background thread, which will watch out for blocking too long. Here + * we will arm a tasklet for it and synchronize delivery of interrupt properly. + */ + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void close() { + disruptor.shutdown(); + executor.shutdown(); + } +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMNotificationRouterEvent.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMNotificationRouterEvent.java new file mode 100644 index 0000000000..65c7166ac9 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMNotificationRouterEvent.java @@ -0,0 +1,59 @@ +/* + * 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.md.sal.dom.broker.impl; + +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import com.lmax.disruptor.EventFactory; +import java.util.Collection; +import org.opendaylight.controller.md.sal.dom.api.DOMNotification; +import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener; +import org.opendaylight.yangtools.concepts.ListenerRegistration; + +/** + * A single notification event in the disruptor ringbuffer. These objects are reused, + * so they do have mutable state. + */ +final class DOMNotificationRouterEvent { + public static final EventFactory FACTORY = new EventFactory() { + @Override + public DOMNotificationRouterEvent newInstance() { + return new DOMNotificationRouterEvent(); + } + }; + + private Collection> subscribers; + private DOMNotification notification; + private SettableFuture future; + + private DOMNotificationRouterEvent() { + // Hidden on purpose, initialized in initialize() + } + + ListenableFuture initialize(final DOMNotification notification, final Collection> subscribers) { + this.notification = Preconditions.checkNotNull(notification); + this.subscribers = Preconditions.checkNotNull(subscribers); + this.future = SettableFuture.create(); + return this.future; + } + + void deliverNotification() { + for (ListenerRegistration r : subscribers) { + final DOMNotificationListener l = r.getInstance(); + if (l != null) { + l.onNotification(notification); + } + } + } + + void setFuture() { + future.set(null); + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouter.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouter.java new file mode 100644 index 0000000000..d72f714a5f --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouter.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.broker.impl; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import javax.annotation.concurrent.GuardedBy; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcAvailabilityListener; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationRegistration; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcProviderService; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; +import org.opendaylight.controller.md.sal.dom.spi.AbstractDOMRpcImplementationRegistration; +import org.opendaylight.yangtools.concepts.AbstractListenerRegistration; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +public final class DOMRpcRouter implements AutoCloseable, DOMRpcService, DOMRpcProviderService, SchemaContextListener { + private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder().setNameFormat("DOMRpcRouter-listener-%s").setDaemon(true).build(); + private final ExecutorService listenerNotifier = Executors.newSingleThreadExecutor(THREAD_FACTORY); + @GuardedBy("this") + private Collection> listeners = Collections.emptyList(); + private volatile DOMRpcRoutingTable routingTable = DOMRpcRoutingTable.EMPTY; + + @Override + public DOMRpcImplementationRegistration registerRpcImplementation(final T implementation, final DOMRpcIdentifier... rpcs) { + return registerRpcImplementation(implementation, ImmutableSet.copyOf(rpcs)); + } + + private static Collection notPresentRpcs(final DOMRpcRoutingTable table, final Collection candidates) { + return ImmutableSet.copyOf(Collections2.filter(candidates, new Predicate() { + @Override + public boolean apply(final DOMRpcIdentifier input) { + return !table.contains(input); + } + })); + } + + private synchronized void removeRpcImplementation(final DOMRpcImplementation implementation, final Set rpcs) { + final DOMRpcRoutingTable oldTable = routingTable; + final DOMRpcRoutingTable newTable = oldTable.remove(implementation, rpcs); + + final Collection removedRpcs = notPresentRpcs(newTable, rpcs); + final Collection> capturedListeners = listeners; + routingTable = newTable; + + listenerNotifier.execute(new Runnable() { + @Override + public void run() { + for (ListenerRegistration l : capturedListeners) { + // Need to ensure removed listeners do not get notified + synchronized (DOMRpcRouter.this) { + if (listeners.contains(l)) { + l.getInstance().onRpcUnavailable(removedRpcs); + } + } + } + } + }); + } + + @Override + public synchronized DOMRpcImplementationRegistration registerRpcImplementation(final T implementation, final Set rpcs) { + final DOMRpcRoutingTable oldTable = routingTable; + final DOMRpcRoutingTable newTable = oldTable.add(implementation, rpcs); + + final Collection addedRpcs = notPresentRpcs(oldTable, rpcs); + final Collection> capturedListeners = listeners; + routingTable = newTable; + + listenerNotifier.execute(new Runnable() { + @Override + public void run() { + for (ListenerRegistration l : capturedListeners) { + // Need to ensure removed listeners do not get notified + synchronized (DOMRpcRouter.this) { + if (listeners.contains(l)) { + l.getInstance().onRpcAvailable(addedRpcs); + } + } + } + } + }); + + return new AbstractDOMRpcImplementationRegistration(implementation) { + @Override + protected void removeRegistration() { + removeRpcImplementation(getInstance(), rpcs); + } + }; + } + + @Override + public CheckedFuture invokeRpc(final SchemaPath type, final NormalizedNode input) { + return routingTable.invokeRpc(type, input); + } + + private synchronized void removeListener(final ListenerRegistration reg) { + listeners = ImmutableList.copyOf(Collections2.filter(listeners, new Predicate() { + @Override + public boolean apply(final Object input) { + return !reg.equals(input); + } + })); + } + + @Override + public synchronized ListenerRegistration registerRpcListener(final T listener) { + final ListenerRegistration ret = new AbstractListenerRegistration(listener) { + @Override + protected void removeRegistration() { + removeListener(this); + } + }; + + final Builder> b = ImmutableList.builder(); + b.addAll(listeners); + b.add(ret); + listeners = b.build(); + final Map> capturedRpcs = routingTable.getRpcs(); + + listenerNotifier.execute(new Runnable() { + @Override + public void run() { + for (final Entry> e : capturedRpcs.entrySet()) { + listener.onRpcAvailable(Collections2.transform(e.getValue(), new Function() { + @Override + public DOMRpcIdentifier apply(final YangInstanceIdentifier input) { + return DOMRpcIdentifier.create(e.getKey(), input); + } + })); + } + } + }); + + return ret; + } + + @Override + public synchronized void onGlobalContextUpdated(final SchemaContext context) { + final DOMRpcRoutingTable oldTable = routingTable; + final DOMRpcRoutingTable newTable = oldTable.setSchemaContext(context); + routingTable = newTable; + } + + @Override + public void close() { + listenerNotifier.shutdown(); + } + +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRoutingTable.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRoutingTable.java new file mode 100644 index 0000000000..0e5ce271e4 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRoutingTable.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.broker.impl; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +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.model.api.SchemaPath; +import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode; + +final class DOMRpcRoutingTable { + private static final QName CONTEXT_REFERENCE = QName.cachedReference(QName.create("urn:opendaylight:yang:extension:yang-ext", "2013-07-09", "context-reference")); + + static final DOMRpcRoutingTable EMPTY = new DOMRpcRoutingTable(); + private static final Function> EXTRACT_IDENTIFIERS = + new Function>() { + @Override + public Set apply(final AbstractDOMRpcRoutingTableEntry input) { + return input.registeredIdentifiers(); + } + }; + private final Map rpcs; + private final SchemaContext schemaContext; + + private DOMRpcRoutingTable() { + rpcs = Collections.emptyMap(); + schemaContext = null; + } + + private DOMRpcRoutingTable(final Map rpcs, final SchemaContext schemaContext) { + this.rpcs = Preconditions.checkNotNull(rpcs); + this.schemaContext = schemaContext; + } + + private static ListMultimap decomposeIdentifiers(final Set rpcs) { + final ListMultimap ret = LinkedListMultimap.create(); + for (DOMRpcIdentifier i : rpcs) { + ret.put(i.getType(), i.getContextReference()); + } + return ret; + } + + DOMRpcRoutingTable add(final DOMRpcImplementation implementation, final Set rpcs) { + if (rpcs.isEmpty()) { + return this; + } + + // First decompose the identifiers to a multimap + final ListMultimap toAdd = decomposeIdentifiers(rpcs); + + // Now iterate over existing entries, modifying them as appropriate... + final Builder mb = ImmutableMap.builder(); + for (Entry re : this.rpcs.entrySet()) { + List newRpcs = toAdd.removeAll(re.getKey()); + if (!newRpcs.isEmpty()) { + final AbstractDOMRpcRoutingTableEntry ne = re.getValue().add(implementation, newRpcs); + mb.put(re.getKey(), ne); + } else { + mb.put(re); + } + } + + // Finally add whatever is left in the decomposed multimap + for (Entry> e : toAdd.asMap().entrySet()) { + final Builder> vb = ImmutableMap.builder(); + final List v = Collections.singletonList(implementation); + for (YangInstanceIdentifier i : e.getValue()) { + vb.put(i, v); + } + + mb.put(e.getKey(), createRpcEntry(schemaContext, e.getKey(), vb.build())); + } + + return new DOMRpcRoutingTable(mb.build(), schemaContext); + } + + DOMRpcRoutingTable remove(final DOMRpcImplementation implementation, final Set rpcs) { + if (rpcs.isEmpty()) { + return this; + } + + // First decompose the identifiers to a multimap + final ListMultimap toRemove = decomposeIdentifiers(rpcs); + + // Now iterate over existing entries, modifying them as appropriate... + final Builder b = ImmutableMap.builder(); + for (Entry e : this.rpcs.entrySet()) { + final List removed = toRemove.removeAll(e.getKey()); + if (!removed.isEmpty()) { + final AbstractDOMRpcRoutingTableEntry ne = e.getValue().remove(implementation, removed); + if (ne != null) { + b.put(e.getKey(), ne); + } + } else { + b.put(e); + } + } + + // All done, whatever is in toRemove, was not there in the first place + return new DOMRpcRoutingTable(b.build(), schemaContext); + } + + boolean contains(final DOMRpcIdentifier input) { + final AbstractDOMRpcRoutingTableEntry contexts = rpcs.get(input.getType()); + return contexts != null && contexts.containsContext(input.getContextReference()); + } + + Map> getRpcs() { + return Maps.transformValues(rpcs, EXTRACT_IDENTIFIERS); + } + + private static RpcDefinition findRpcDefinition(final SchemaContext context, final SchemaPath schemaPath) { + if (context != null) { + final QName qname = schemaPath.getPathFromRoot().iterator().next(); + final Module module = context.findModuleByNamespaceAndRevision(qname.getNamespace(), qname.getRevision()); + if (module != null && module.getRpcs() != null) { + for (RpcDefinition rpc : module.getRpcs()) { + if (qname.equals(rpc.getQName())) { + return rpc; + } + } + } + } + + return null; + } + + private static AbstractDOMRpcRoutingTableEntry createRpcEntry(final SchemaContext context, final SchemaPath key, final Map> implementations) { + final RpcDefinition rpcDef = findRpcDefinition(context, key); + if (rpcDef != null) { + final ContainerSchemaNode input = rpcDef.getInput(); + if (input != null) { + for (DataSchemaNode c : input.getChildNodes()) { + for (UnknownSchemaNode extension : c.getUnknownSchemaNodes()) { + if (CONTEXT_REFERENCE.equals(extension.getNodeType())) { + final YangInstanceIdentifier keyId = YangInstanceIdentifier.builder().node(input.getQName()).node(c.getQName()).build(); + return new RoutedDOMRpcRoutingTableEntry(rpcDef, keyId, implementations); + } + } + } + } + + return new GlobalDOMRpcRoutingTableEntry(rpcDef, implementations); + } else { + return new UnknownDOMRpcRoutingTableEntry(key, implementations); + } + } + + CheckedFuture invokeRpc(final SchemaPath type, final NormalizedNode input) { + final AbstractDOMRpcRoutingTableEntry entry = rpcs.get(type); + if (entry == null) { + return Futures.immediateFailedCheckedFuture(new DOMRpcImplementationNotAvailableException("No implementation of RPC %s available", type)); + } + + return entry.invokeRpc(input); + } + + DOMRpcRoutingTable setSchemaContext(final SchemaContext context) { + final Builder b = ImmutableMap.builder(); + + for (Entry e : rpcs.entrySet()) { + b.put(e.getKey(), createRpcEntry(context, e.getKey(), e.getValue().getImplementations())); + } + + return new DOMRpcRoutingTable(b.build(), context); + } + +} diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/GlobalDOMRpcRoutingTableEntry.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/GlobalDOMRpcRoutingTableEntry.java new file mode 100644 index 0000000000..3b0d5df964 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/GlobalDOMRpcRoutingTableEntry.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.broker.impl; + +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.CheckedFuture; +import java.util.List; +import java.util.Map; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; + +final class GlobalDOMRpcRoutingTableEntry extends AbstractDOMRpcRoutingTableEntry { + private final DOMRpcIdentifier rpcId; + + private GlobalDOMRpcRoutingTableEntry(final DOMRpcIdentifier rpcId, final Map> impls) { + super(rpcId.getType(), impls); + this.rpcId = Preconditions.checkNotNull(rpcId); + } + + // We do not need the RpcDefinition, but this makes sure we do not + // forward something we don't know to be an RPC. + GlobalDOMRpcRoutingTableEntry(final RpcDefinition def, final Map> impls) { + super(def.getPath(), impls); + this.rpcId = DOMRpcIdentifier.create(def.getPath()); + } + + @Override + protected CheckedFuture invokeRpc(final NormalizedNode input) { + return getImplementations(null).get(0).invokeRpc(rpcId, input); + } + + @Override + protected GlobalDOMRpcRoutingTableEntry newInstance(final Map> impls) { + return new GlobalDOMRpcRoutingTableEntry(rpcId, impls); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransaction.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransaction.java index fdb80ebcbe..51b28e1450 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransaction.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransaction.java @@ -7,8 +7,8 @@ */ package org.opendaylight.controller.md.sal.dom.broker.impl; -import com.google.common.base.Objects; -import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.FutureCallback; @@ -73,7 +73,7 @@ final class PingPongTransaction implements FutureCallback { @Override public String toString() { - return addToStringAttributes(Objects.toStringHelper(this)).toString(); + return addToStringAttributes(MoreObjects.toStringHelper(this)).toString(); } protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) { diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransactionChain.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransactionChain.java index c3a56ed454..961b6c7b93 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransactionChain.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransactionChain.java @@ -235,7 +235,7 @@ public final class PingPongTransactionChain implements DOMTransactionChain { */ final boolean success = READY_UPDATER.compareAndSet(this, null, tx); Preconditions.checkState(success, "Transaction %s collided on ready state", tx, readyTx); - LOG.debug("Transaction {} readied"); + LOG.debug("Transaction {} readied", tx); /* * We do not see a transaction being in-flight, so we need to take care of dispatching diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/RoutedDOMRpcRoutingTableEntry.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/RoutedDOMRpcRoutingTableEntry.java new file mode 100644 index 0000000000..e6df966f36 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/RoutedDOMRpcRoutingTableEntry.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.broker.impl; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import java.util.List; +import java.util.Map; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class RoutedDOMRpcRoutingTableEntry extends AbstractDOMRpcRoutingTableEntry { + private static final Logger LOG = LoggerFactory.getLogger(RoutedDOMRpcRoutingTableEntry.class); + private final DOMRpcIdentifier globalRpcId; + private final YangInstanceIdentifier keyId; + + private RoutedDOMRpcRoutingTableEntry(final DOMRpcIdentifier globalRpcId, final YangInstanceIdentifier keyId, final Map> impls) { + super(globalRpcId.getType(), impls); + this.keyId = Preconditions.checkNotNull(keyId); + this.globalRpcId = Preconditions.checkNotNull(globalRpcId); + } + + RoutedDOMRpcRoutingTableEntry(final RpcDefinition def, final YangInstanceIdentifier keyId, final Map> impls) { + super(def.getPath(), impls); + this.keyId = Preconditions.checkNotNull(keyId); + this.globalRpcId = DOMRpcIdentifier.create(def.getPath()); + } + + @Override + protected CheckedFuture invokeRpc(final NormalizedNode input) { + final Optional> maybeKey = NormalizedNodes.findNode(input, keyId); + + // Routing key is present, attempt to deliver as a routed RPC + if (maybeKey.isPresent()) { + final NormalizedNode key = maybeKey.get(); + final Object value = key.getValue(); + if (value instanceof YangInstanceIdentifier) { + final YangInstanceIdentifier iid = (YangInstanceIdentifier) value; + final List impls = getImplementations(iid); + if (impls != null) { + return impls.get(0).invokeRpc(DOMRpcIdentifier.create(getSchemaPath(), iid), input); + } + LOG.debug("No implementation for context {} found", iid); + } else { + LOG.warn("Ignoring wrong context value {}", value); + } + } + + final List impls = getImplementations(null); + if (impls != null) { + return impls.get(0).invokeRpc(globalRpcId, input); + } else { + return Futures.immediateFailedCheckedFuture(new DOMRpcImplementationNotAvailableException("No implementation of RPC %s available", getSchemaPath())); + } + } + + @Override + protected RoutedDOMRpcRoutingTableEntry newInstance(final Map> impls) { + return new RoutedDOMRpcRoutingTableEntry(globalRpcId, keyId, impls); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/UnknownDOMRpcRoutingTableEntry.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/UnknownDOMRpcRoutingTableEntry.java new file mode 100644 index 0000000000..aadfa4a507 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/UnknownDOMRpcRoutingTableEntry.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.broker.impl; + +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import java.util.List; +import java.util.Map; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +final class UnknownDOMRpcRoutingTableEntry extends AbstractDOMRpcRoutingTableEntry { + private final CheckedFuture unknownRpc; + + UnknownDOMRpcRoutingTableEntry(final SchemaPath schemaPath, final Map> impls) { + super(schemaPath, impls); + unknownRpc = Futures.immediateFailedCheckedFuture( + new DOMRpcImplementationNotAvailableException("SchemaPath %s is not resolved to an RPC", schemaPath)); + } + + @Override + protected CheckedFuture invokeRpc(final NormalizedNode input) { + return unknownRpc; + } + + @Override + protected UnknownDOMRpcRoutingTableEntry newInstance(final Map> impls) { + return new UnknownDOMRpcRoutingTableEntry(getSchemaPath(), impls); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMBrokerPerformanceTest.java b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMBrokerPerformanceTest.java index 2f02f981ab..2a7c8b6682 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMBrokerPerformanceTest.java +++ b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMBrokerPerformanceTest.java @@ -60,8 +60,8 @@ public class DOMBrokerPerformanceTest { @Before public void setupStore() { - InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER", MoreExecutors.sameThreadExecutor()); - InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG", MoreExecutors.sameThreadExecutor()); + InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER", MoreExecutors.newDirectExecutorService()); + InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG", MoreExecutors.newDirectExecutorService()); schemaContext = TestModel.createTestContext(); operStore.onGlobalContextUpdated(schemaContext); diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMBrokerTest.java b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMBrokerTest.java index c1d301c549..cd8ac09980 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMBrokerTest.java +++ b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMBrokerTest.java @@ -57,9 +57,9 @@ public class DOMBrokerTest { public void setupStore() { InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER", - MoreExecutors.sameThreadExecutor()); + MoreExecutors.newDirectExecutorService()); InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG", - MoreExecutors.sameThreadExecutor()); + MoreExecutors.newDirectExecutorService()); schemaContext = TestModel.createTestContext(); operStore.onGlobalContextUpdated(schemaContext); diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMTransactionChainTest.java b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMTransactionChainTest.java index 03d39a2a62..aba78f7559 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMTransactionChainTest.java +++ b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMTransactionChainTest.java @@ -41,8 +41,8 @@ public class DOMTransactionChainTest { @Before public void setupStore() { - InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER", MoreExecutors.sameThreadExecutor()); - InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG", MoreExecutors.sameThreadExecutor()); + InMemoryDOMDataStore operStore = new InMemoryDOMDataStore("OPER", MoreExecutors.newDirectExecutorService()); + InMemoryDOMDataStore configStore = new InMemoryDOMDataStore("CFG", MoreExecutors.newDirectExecutorService()); schemaContext = TestModel.createTestContext(); operStore.onGlobalContextUpdated(schemaContext); diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/AbstractDOMNotificationListenerRegistration.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/AbstractDOMNotificationListenerRegistration.java deleted file mode 100644 index 2934b0da35..0000000000 --- a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/AbstractDOMNotificationListenerRegistration.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.md.sal.dom.spi; - -import javax.annotation.Nonnull; -import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener; -import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListenerRegistration; -import org.opendaylight.yangtools.concepts.AbstractListenerRegistration; - -/** - * Utility base class for {@link DOMNotificationListenerRegistration} - * implementations. - */ -public abstract class AbstractDOMNotificationListenerRegistration extends AbstractListenerRegistration implements DOMNotificationListenerRegistration { - /** - * Default constructor. Subclasses need to invoke it from their - * constructor(s). - * - * @param listener {@link DOMNotificationListener} instance which is - * being held by this registration. May not be null. - */ - protected AbstractDOMNotificationListenerRegistration(final @Nonnull DOMNotificationListener listener) { - super(listener); - } -} diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/AbstractDOMRpcImplementationRegistration.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/AbstractDOMRpcImplementationRegistration.java new file mode 100644 index 0000000000..f0ce2b69c7 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/AbstractDOMRpcImplementationRegistration.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.spi; + +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationRegistration; +import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; + +/** + * Abstract base class for {@link DOMRpcImplementationRegistration} implementations. + */ +public abstract class AbstractDOMRpcImplementationRegistration extends AbstractObjectRegistration implements DOMRpcImplementationRegistration { + protected AbstractDOMRpcImplementationRegistration(final T instance) { + super(instance); + } +} diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/AbstractDOMRpcProviderService.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/AbstractDOMRpcProviderService.java new file mode 100644 index 0000000000..c1374264cc --- /dev/null +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/AbstractDOMRpcProviderService.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.spi; + +import com.google.common.collect.ImmutableSet; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationRegistration; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcProviderService; + +/** + * Convenience abstract base class for {@link DOMRpcProviderService} implementations. + */ +public abstract class AbstractDOMRpcProviderService implements DOMRpcProviderService { + @Override + public final DOMRpcImplementationRegistration registerRpcImplementation(final T implementation, final DOMRpcIdentifier... types) { + return registerRpcImplementation(implementation, ImmutableSet.copyOf(types)); + } +} diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/DefaultDOMRpcResult.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/DefaultDOMRpcResult.java new file mode 100644 index 0000000000..269fd3537c --- /dev/null +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/DefaultDOMRpcResult.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.spi; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * Utility class implementing {@link DefaultDOMRpcResult}. + */ +@Beta +public final class DefaultDOMRpcResult implements DOMRpcResult, Immutable, Serializable { + private static final long serialVersionUID = 1L; + private final Collection errors; + private final NormalizedNode result; + + private static Collection asCollection(final RpcError... errors) { + if (errors.length == 0) { + return Collections.emptyList(); + } else { + return Arrays.asList(errors); + } + } + + public DefaultDOMRpcResult(final NormalizedNode result, final RpcError... errors) { + this(result, asCollection(errors)); + } + + public DefaultDOMRpcResult(final RpcError... errors) { + this(null, asCollection(errors)); + } + + public DefaultDOMRpcResult(final NormalizedNode result) { + this(result, Collections.emptyList()); + } + + public DefaultDOMRpcResult(final NormalizedNode result, final @Nonnull Collection errors) { + this.result = result; + this.errors = Preconditions.checkNotNull(errors); + } + + public DefaultDOMRpcResult(final @Nonnull Collection errors) { + this(null, errors); + } + + @Override + public @Nonnull Collection getErrors() { + return errors; + } + + @Override + public NormalizedNode getResult() { + return result; + } + + @Override + public int hashCode() { + int ret = errors.hashCode(); + if (result != null) { + ret = 31 * ret + result.hashCode(); + } + return ret; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DefaultDOMRpcResult)) { + return false; + } + + final DefaultDOMRpcResult other = (DefaultDOMRpcResult) obj; + if (!errors.equals(other.errors)) { + return false; + } + return Objects.equals(result, other.result); + } +} diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMNotificationService.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMNotificationService.java index 5199a3893b..b40c428b6b 100644 --- a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMNotificationService.java +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMNotificationService.java @@ -10,8 +10,8 @@ package org.opendaylight.controller.md.sal.dom.spi; import com.google.common.collect.ForwardingObject; import java.util.Collection; import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener; -import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListenerRegistration; import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService; +import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.model.api.SchemaPath; /** @@ -23,13 +23,13 @@ public abstract class ForwardingDOMNotificationService extends ForwardingObject protected abstract DOMNotificationService delegate(); @Override - public DOMNotificationListenerRegistration registerNotificationListener(final DOMNotificationListener listener, + public ListenerRegistration registerNotificationListener(final T listener, final Collection types) { return delegate().registerNotificationListener(listener, types); } @Override - public DOMNotificationListenerRegistration registerNotificationListener(final DOMNotificationListener listener, + public ListenerRegistration registerNotificationListener(final T listener, final SchemaPath... types) { return delegate().registerNotificationListener(listener, types); } diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcImplementation.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcImplementation.java new file mode 100644 index 0000000000..e93f9417d4 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcImplementation.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.spi; + +import com.google.common.collect.ForwardingObject; +import com.google.common.util.concurrent.CheckedFuture; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * Utility implementation which implements {@link DOMRpcImplementation} by forwarding it to + * a backing delegate. + */ +public abstract class ForwardingDOMRpcImplementation extends ForwardingObject implements DOMRpcImplementation { + @Override + protected abstract @Nonnull DOMRpcImplementation delegate(); + + @Override + public CheckedFuture invokeRpc(final DOMRpcIdentifier type, final NormalizedNode input) { + return delegate().invokeRpc(type, input); + } +} diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcProviderService.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcProviderService.java new file mode 100644 index 0000000000..99c4cad8a9 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcProviderService.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.spi; + +import com.google.common.collect.ForwardingObject; +import java.util.Set; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationRegistration; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcProviderService; + +/** + * Utility class which implements {@link DOMRpcProviderService} by forwarding + * requests to a backing instance. + */ +public abstract class ForwardingDOMRpcProviderService extends ForwardingObject implements DOMRpcProviderService { + @Override + protected abstract @Nonnull DOMRpcProviderService delegate(); + + @Override + public DOMRpcImplementationRegistration registerRpcImplementation(final T implementation, final DOMRpcIdentifier... types) { + return delegate().registerRpcImplementation(implementation, types); + } + + @Override + public DOMRpcImplementationRegistration registerRpcImplementation(final T implementation, final Set types) { + return delegate().registerRpcImplementation(implementation, types); + } +} diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcResult.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcResult.java new file mode 100644 index 0000000000..ba46d3facf --- /dev/null +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcResult.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.spi; + +import com.google.common.collect.ForwardingObject; +import java.util.Collection; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * Utility class which implements {@link DOMRpcResult} by forwarding all methods + * to a backing instance. + */ +public abstract class ForwardingDOMRpcResult extends ForwardingObject implements DOMRpcResult { + @Override + protected abstract @Nonnull DOMRpcResult delegate(); + + @Override + public Collection getErrors() { + return delegate().getErrors(); + } + + @Override + public NormalizedNode getResult() { + return delegate().getResult(); + } +} diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcService.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcService.java new file mode 100644 index 0000000000..976d08623b --- /dev/null +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/md/sal/dom/spi/ForwardingDOMRpcService.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.dom.spi; + +import com.google.common.collect.ForwardingObject; +import com.google.common.util.concurrent.CheckedFuture; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcAvailabilityListener; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcException; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; +import org.opendaylight.controller.md.sal.dom.api.DOMRpcService; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; + +/** + * Utility {@link DOMRpcService} which forwards all requests to a backing delegate instance. + */ +public abstract class ForwardingDOMRpcService extends ForwardingObject implements DOMRpcService { + @Override + protected abstract @Nonnull DOMRpcService delegate(); + + @Override + public CheckedFuture invokeRpc(final SchemaPath type, final NormalizedNode input) { + return delegate().invokeRpc(type, input); + } + + @Override + public ListenerRegistration registerRpcListener(final T listener) { + return delegate().registerRpcListener(listener); + } +} diff --git a/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/sal/core/spi/data/DOMStoreTreeChangePublisher.java b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/sal/core/spi/data/DOMStoreTreeChangePublisher.java new file mode 100644 index 0000000000..5d75f88fb9 --- /dev/null +++ b/opendaylight/md-sal/sal-dom-spi/src/main/java/org/opendaylight/controller/sal/core/spi/data/DOMStoreTreeChangePublisher.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.sal.core.spi.data; + +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +/** + * Interface implemented by DOMStore implementations which allow registration + * of {@link DOMDataTreeChangeListener} instances. + */ +public interface DOMStoreTreeChangePublisher { + /** + * Registers a {@link DOMDataTreeChangeListener} to receive + * notifications when data changes under a given path in the conceptual data + * tree. + *

    + * You are able to register for notifications for any node or subtree + * which can be represented using {@link YangInstanceIdentifier}. + *

    + * + * You are able to register for data change notifications for a subtree or leaf + * even if it does not exist. You will receive notification once that node is + * created. + *

    + * If there is any pre-existing data in data tree on path for which you are + * registering, you will receive initial data change event, which will + * contain all pre-existing data, marked as created. + * + *

    + * This method returns a {@link ListenerRegistration} object. To + * "unregister" your listener for changes call the {@link ListenerRegistration#close()} + * method on this returned object. + *

    + * You MUST explicitly unregister your listener when you no longer want to receive + * notifications. This is especially true in OSGi environments, where failure to + * do so during bundle shutdown can lead to stale listeners being still registered. + * + * @param treeId + * Data tree identifier of the subtree which should be watched for + * changes. + * @param listener + * Listener instance which is being registered + * @return Listener registration object, which may be used to unregister + * your listener using {@link ListenerRegistration#close()} to stop + * delivery of change events. + */ + @Nonnull ListenerRegistration registerTreeChangeListener(@Nonnull YangInstanceIdentifier treeId, @Nonnull L listener); +} diff --git a/opendaylight/md-sal/sal-dom-xsql/pom.xml b/opendaylight/md-sal/sal-dom-xsql/pom.xml index c7fd20248d..f0e27cf2ac 100644 --- a/opendaylight/md-sal/sal-dom-xsql/pom.xml +++ b/opendaylight/md-sal/sal-dom-xsql/pom.xml @@ -48,6 +48,10 @@ org.osgi org.osgi.core + + junit + junit + diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/README b/opendaylight/md-sal/sal-dummy-distributed-datastore/README new file mode 100644 index 0000000000..19a17eacce --- /dev/null +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/README @@ -0,0 +1,16 @@ +To use this run a real instance of the controller on your laptop. +Modify the module-shards.conf to replicate modules to member-2 or +member-2 and member-3 as neccessary. + +Then run the dummy datastore. + +For example, + + java -jar ./target/sal-dummy-distributed-datastore-1.2.0-SNAPSHOT-allinone.jar -member-name member-2 -cause-trouble -drop-replies -max-delay-millis 500 + +Runs the dummy datastore as member-2. Will cause failures including dropped replies and when it does reply may cause a random delay of upto +500 millis + +This will start of the dummy datastore which will then spawn dummy shard actors which will listen to the RequestVote +and AppendEntries messages. For RequestVote messages it will always respond with a positive vote and for AppendEntries +it will put a sleep for a randomized interval upto the max delay. \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml b/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml new file mode 100644 index 0000000000..85ae59b161 --- /dev/null +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml @@ -0,0 +1,215 @@ + + + 4.0.0 + + org.opendaylight.controller + sal-parent + 1.2.0-SNAPSHOT + + sal-dummy-distributed-datastore + bundle + + + + com.google.guava + guava + + + + com.typesafe.akka + akka-actor_${scala.version} + + + + com.typesafe.akka + akka-cluster_${scala.version} + + + + com.typesafe.akka + akka-persistence-experimental_${scala.version} + + + + com.typesafe.akka + akka-remote_${scala.version} + + + + com.typesafe.akka + akka-testkit_${scala.version} + + + + com.typesafe.akka + akka-slf4j_${scala.version} + + + + com.typesafe.akka + akka-osgi_${scala.version} + + + + org.opendaylight.controller + sal-clustering-commons + + + + org.opendaylight.controller + sal-akka-raft + 1.2.0-SNAPSHOT + + + + org.opendaylight.controller + sal-distributed-datastore + + + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + org.slf4j + slf4j-simple + test + + + + args4j + args4j + 2.0.29 + + + + org.slf4j + slf4j-simple + + + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + ${project.groupId}.${project.artifactId} + + + !*snappy;!org.jboss.*;!com.jcraft.*;!*jetty*;!sun.security.*;* + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.jacoco + jacoco-maven-plugin + + + org.opendaylight.controller.* + + + + org.opendaylight.controller.config.yang.config.* + + false + + + + pre-test + + prepare-agent + + + + post-test + + report + + test + + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + true + allinone + + + *:* + + + + + reference.conf + + + + org.opendaylight.controller.dummy.datastore.Main + + + + + + + + + + + scm:git:ssh://git.opendaylight.org:29418/controller.git + scm:git:ssh://git.opendaylight.org:29418/controller.git + HEAD + https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL:Architecture:Clustering + + diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/Configuration.java b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/Configuration.java new file mode 100644 index 0000000000..bbf40824a6 --- /dev/null +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/Configuration.java @@ -0,0 +1,33 @@ +/* + * 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.dummy.datastore; + +public class Configuration { + private final int maxDelayInMillis; + private final boolean dropReplies; + private final boolean causeTrouble; + + public Configuration(int maxDelayInMillis, boolean dropReplies, boolean causeTrouble) { + this.maxDelayInMillis = maxDelayInMillis; + this.dropReplies = dropReplies; + this.causeTrouble = causeTrouble; + } + + public int getMaxDelayInMillis() { + return maxDelayInMillis; + } + + public boolean shouldDropReplies() { + return dropReplies; + } + + public boolean shouldCauseTrouble() { + return causeTrouble; + } +} diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/DummyShard.java b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/DummyShard.java new file mode 100644 index 0000000000..0b72a32f10 --- /dev/null +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/DummyShard.java @@ -0,0 +1,133 @@ +/* + * 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.dummy.datastore; + +import akka.actor.Props; +import akka.actor.UntypedActor; +import akka.japi.Creator; +import com.google.common.base.Stopwatch; +import java.util.concurrent.TimeUnit; +import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry; +import org.opendaylight.controller.cluster.raft.messages.AppendEntries; +import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; +import org.opendaylight.controller.cluster.raft.messages.InstallSnapshot; +import org.opendaylight.controller.cluster.raft.messages.InstallSnapshotReply; +import org.opendaylight.controller.cluster.raft.messages.RequestVote; +import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DummyShard extends UntypedActor{ + private final Configuration configuration; + private final String followerId; + private final Logger LOG = LoggerFactory.getLogger(DummyShard.class); + private long lastMessageIndex = -1; + private long lastMessageSize = 0; + private Stopwatch appendEntriesWatch; + + public DummyShard(Configuration configuration, String followerId) { + this.configuration = configuration; + this.followerId = followerId; + LOG.info("Creating : {}", followerId); + } + + @Override + public void onReceive(Object o) throws Exception { + if(o instanceof RequestVote){ + RequestVote req = (RequestVote) o; + sender().tell(new RequestVoteReply(req.getTerm(), true), self()); + } else if(AppendEntries.LEGACY_SERIALIZABLE_CLASS.equals(o.getClass()) || o instanceof AppendEntries) { + AppendEntries req = AppendEntries.fromSerializable(o); + handleAppendEntries(req); + } else if(InstallSnapshot.SERIALIZABLE_CLASS.equals(o.getClass())) { + InstallSnapshot req = InstallSnapshot.fromSerializable(o); + handleInstallSnapshot(req); + } else if(o instanceof InstallSnapshot){ + handleInstallSnapshot((InstallSnapshot) o); + } else { + LOG.error("Unknown message : {}", o.getClass()); + } + } + + private void handleInstallSnapshot(InstallSnapshot req) { + sender().tell(new InstallSnapshotReply(req.getTerm(), followerId, req.getChunkIndex(), true), self()); + } + + protected void handleAppendEntries(AppendEntries req) throws InterruptedException { + LOG.info("{} - Received AppendEntries message : leader term = {}, index = {}, prevLogIndex = {}, size = {}", + followerId, req.getTerm(),req.getLeaderCommit(), req.getPrevLogIndex(), req.getEntries().size()); + + if(appendEntriesWatch != null){ + long elapsed = appendEntriesWatch.elapsed(TimeUnit.SECONDS); + if(elapsed >= 5){ + LOG.error("More than 5 seconds since last append entry, elapsed Time = {} seconds" + + ", leaderCommit = {}, prevLogIndex = {}, size = {}", + elapsed, req.getLeaderCommit(), req.getPrevLogIndex(), req.getEntries().size()); + } + appendEntriesWatch.reset().start(); + } else { + appendEntriesWatch = Stopwatch.createStarted(); + } + + if(lastMessageIndex == req.getLeaderCommit() && req.getEntries().size() > 0 && lastMessageSize > 0){ + LOG.error("{} - Duplicate message with leaderCommit = {} prevLogIndex = {} received", followerId, req.getLeaderCommit(), req.getPrevLogIndex()); + } + + lastMessageIndex = req.getLeaderCommit(); + lastMessageSize = req.getEntries().size(); + + long lastIndex = req.getLeaderCommit(); + if (req.getEntries().size() > 0) { + for(ReplicatedLogEntry entry : req.getEntries()) { + lastIndex = entry.getIndex(); + } + } + + if (configuration.shouldCauseTrouble() && req.getEntries().size() > 0) { + boolean ignore = false; + + if (configuration.shouldDropReplies()) { + ignore = Math.random() > 0.5; + } + + long delay = (long) (Math.random() * configuration.getMaxDelayInMillis()); + + if (!ignore) { + LOG.info("{} - Randomizing delay : {}", followerId, delay); + Thread.sleep(delay); + sender().tell(new AppendEntriesReply(followerId, req.getTerm(), true, lastIndex, req.getTerm()), self()); + } + } else { + sender().tell(new AppendEntriesReply(followerId, req.getTerm(), true, lastIndex, req.getTerm()), self()); + } + } + + public static Props props(Configuration configuration, final String followerId) { + + return Props.create(new DummyShardCreator(configuration, followerId)); + } + + private static class DummyShardCreator implements Creator { + + private static final long serialVersionUID = 1L; + private final Configuration configuration; + private final String followerId; + + DummyShardCreator(Configuration configuration, String followerId) { + this.configuration = configuration; + this.followerId = followerId; + } + + @Override + public DummyShard create() throws Exception { + return new DummyShard(configuration, followerId); + } + } + +} diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/DummyShardManager.java b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/DummyShardManager.java new file mode 100644 index 0000000000..339f37e0bb --- /dev/null +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/DummyShardManager.java @@ -0,0 +1,72 @@ +/* + * 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.dummy.datastore; + +import akka.actor.ActorContext; +import akka.actor.Props; +import akka.actor.UntypedActor; +import akka.japi.Creator; + +public class DummyShardManager extends UntypedActor { + public DummyShardManager(Configuration configuration, String memberName, String[] shardNames, String type) throws Exception { + new DummyShardsCreator(configuration, context(), memberName, shardNames, type).create(); + } + + @Override + public void onReceive(Object o) throws Exception { + + } + + public static Props props(Configuration configuration, String memberName, String[] shardNames, String type){ + return Props.create(new DummyShardManagerCreator(configuration, memberName, shardNames, type)); + } + + private static class DummyShardManagerCreator implements Creator { + + private final Configuration configuration; + private final String memberName; + private final String[] shardNames; + private final String type; + + public DummyShardManagerCreator(Configuration configuration, String memberName, String[] shardNames, String type) { + this.configuration = configuration; + this.memberName = memberName; + this.shardNames = shardNames; + this.type = type; + } + + @Override + public DummyShardManager create() throws Exception { + return new DummyShardManager(configuration, memberName, shardNames, type ); + } + } + + private static class DummyShardsCreator { + private final Configuration configuration; + private final ActorContext actorSystem; + private final String memberName; + private final String[] shardNames; + private final String type; + + DummyShardsCreator(Configuration configuration, ActorContext actorSystem, String memberName, String[] shardNames, String type){ + this.configuration = configuration; + this.actorSystem = actorSystem; + this.memberName = memberName; + this.shardNames = shardNames; + this.type = type; + } + + void create(){ + for(String shardName : shardNames){ + String shardId = memberName + "-shard-" + shardName + "-" + type; + actorSystem.actorOf(DummyShard.props(configuration, shardId), shardId); + } + } + } +} diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/Main.java b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/Main.java new file mode 100644 index 0000000000..81da1f89a6 --- /dev/null +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/java/org/opendaylight/controller/dummy/datastore/Main.java @@ -0,0 +1,62 @@ +/* + * 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.dummy.datastore; + +import akka.actor.ActorSystem; +import com.typesafe.config.ConfigFactory; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.Option; + +public class Main { + @Option(name="-member-name", usage="Sets the member name", required = true) + public String memberName; + + @Option(name="-max-delay-millis", usage = "Sets the maximum delay that should be applied for any append entry. Only applies when cause-trouble is present.") + public int maxDelayInMillis = 500; + + @Option(name="-cause-trouble", usage="If present turns on artificial failures") + public boolean causeTrouble = false; + + @Option(name="-drop-replies", usage = "If present drops replies. Only applies when cause-trouble is present.") + public boolean dropReplies = false; + + public void run(){ + ActorSystem actorSystem = ActorSystem.create("opendaylight-cluster-data", ConfigFactory.load(memberName).getConfig("odl-cluster-data")); + + Configuration configuration = new Configuration(maxDelayInMillis, dropReplies, causeTrouble); + + actorSystem.actorOf(DummyShardManager.props(configuration, memberName, new String[] {"inventory", "default", "toaster", "topology"}, "operational"), "shardmanager-operational"); + actorSystem.actorOf(DummyShardManager.props(configuration, memberName, new String[] {"inventory", "default", "toaster", "topology"}, "config"), "shardmanager-config"); + } + + @Override + public String toString() { + return "Main{" + + "memberName='" + memberName + '\'' + + ", maxDelayInMillis=" + maxDelayInMillis + + ", causeTrouble=" + causeTrouble + + ", dropReplies=" + dropReplies + + '}'; + } + + public static void main(String[] args){ + Main bean = new Main(); + CmdLineParser parser = new CmdLineParser(bean); + + try { + parser.parseArgument(args); + System.out.println(bean.toString()); + bean.run(); + } catch(Exception e){ + System.err.println(e.getMessage()); + parser.printUsage(System.err); + } + } + +} diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/resources/member-2.conf b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/resources/member-2.conf new file mode 100644 index 0000000000..38aa4c5174 --- /dev/null +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/resources/member-2.conf @@ -0,0 +1,49 @@ +odl-cluster-data { + bounded-mailbox { + mailbox-type = "org.opendaylight.controller.cluster.common.actor.MeteredBoundedMailbox" + mailbox-capacity = 1000 + mailbox-push-timeout-time = 100ms + } + + metric-capture-enabled = true + + akka { + loglevel = "INFO" + loggers = ["akka.event.slf4j.Slf4jLogger"] + + actor { + + provider = "akka.cluster.ClusterActorRefProvider" + serializers { + java = "akka.serialization.JavaSerializer" + proto = "akka.remote.serialization.ProtobufSerializer" + } + + serialization-bindings { + "com.google.protobuf.Message" = proto + + } + } + remote { + log-remote-lifecycle-events = off + netty.tcp { + hostname = "127.0.0.1" + port = 2553 + maximum-frame-size = 419430400 + send-buffer-size = 52428800 + receive-buffer-size = 52428800 + } + } + + cluster { + seed-nodes = ["akka.tcp://opendaylight-cluster-data@127.0.0.1:2550", "akka.tcp://opendaylight-cluster-data@127.0.0.1:2553"] + + auto-down-unreachable-after = 10s + + roles = [ + "member-2" + ] + + } + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/resources/member-3.conf b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/resources/member-3.conf new file mode 100644 index 0000000000..7b2da6a1db --- /dev/null +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/resources/member-3.conf @@ -0,0 +1,49 @@ +odl-cluster-data { + bounded-mailbox { + mailbox-type = "org.opendaylight.controller.cluster.common.actor.MeteredBoundedMailbox" + mailbox-capacity = 1000 + mailbox-push-timeout-time = 100ms + } + + metric-capture-enabled = true + + akka { + loglevel = "INFO" + loggers = ["akka.event.slf4j.Slf4jLogger"] + + actor { + + provider = "akka.cluster.ClusterActorRefProvider" + serializers { + java = "akka.serialization.JavaSerializer" + proto = "akka.remote.serialization.ProtobufSerializer" + } + + serialization-bindings { + "com.google.protobuf.Message" = proto + + } + } + remote { + log-remote-lifecycle-events = off + netty.tcp { + hostname = "127.0.0.1" + port = 2554 + maximum-frame-size = 419430400 + send-buffer-size = 52428800 + receive-buffer-size = 52428800 + } + } + + cluster { + seed-nodes = ["akka.tcp://opendaylight-cluster-data@127.0.0.1:2550", "akka.tcp://opendaylight-cluster-data@127.0.0.1:2554"] + + auto-down-unreachable-after = 10s + + roles = [ + "member-3" + ] + + } + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/resources/simplelogger.properties b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/resources/simplelogger.properties new file mode 100644 index 0000000000..cd2d082079 --- /dev/null +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/src/main/resources/simplelogger.properties @@ -0,0 +1,6 @@ +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=hh:mm:ss,S a +org.slf4j.simpleLogger.logFile=System.out +org.slf4j.simpleLogger.showShortLogName=true +org.slf4j.simpleLogger.levelInBrackets=true +org.slf4j.simpleLogger.defaultLogLevel=error \ No newline at end of file diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDOMStoreTransaction.java b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDOMStoreTransaction.java index 6cc5939047..8d040f612e 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDOMStoreTransaction.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/AbstractDOMStoreTransaction.java @@ -7,10 +7,9 @@ */ package org.opendaylight.controller.md.sal.dom.store.impl; -import com.google.common.base.Objects; -import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Preconditions; - import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransaction; import org.slf4j.Logger; @@ -42,7 +41,7 @@ abstract class AbstractDOMStoreTransaction implements DOMStoreTransaction { @Override public final String toString() { - return addToStringAttributes(Objects.toStringHelper(this)).toString(); + return addToStringAttributes(MoreObjects.toStringHelper(this)).toString(); } /** diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeEventsTask.java b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeEventsTask.java index 25ddbf5df2..14d565c1d0 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeEventsTask.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeEventsTask.java @@ -17,7 +17,7 @@ import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataCh import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder; import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory; import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree; -import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerWalker; import org.opendaylight.yangtools.util.concurrent.NotificationManager; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; @@ -51,7 +51,7 @@ final class ResolveDataChangeEventsTask { * Resolves and submits notification tasks to the specified manager. */ public synchronized void resolve(final NotificationManager, DOMImmutableDataChangeEvent> manager) { - try (final Walker w = listenerRoot.getWalker()) { + try (final ListenerWalker w = listenerRoot.getWalker()) { // Defensive: reset internal state collectedEvents = ArrayListMultimap.create(); diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeState.java b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeState.java index 3db4115af6..6bbed57f39 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeState.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeState.java @@ -7,11 +7,6 @@ */ package org.opendaylight.controller.md.sal.dom.store.impl; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.collect.Iterables; -import com.google.common.collect.Multimap; - import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -22,7 +17,7 @@ import java.util.Map.Entry; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder; -import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerNode; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; @@ -32,6 +27,11 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + /** * Recursion state used in {@link ResolveDataChangeEventsTask}. Instances of this * method track which listeners are affected by a particular change node. It takes @@ -49,7 +49,7 @@ final class ResolveDataChangeState { */ private final Collection inheritedOne; private final YangInstanceIdentifier nodeId; - private final Collection nodes; + private final Collection nodes; private final Map, Builder> subBuilders; private final Map, Builder> oneBuilders; @@ -57,7 +57,7 @@ final class ResolveDataChangeState { private ResolveDataChangeState(final YangInstanceIdentifier nodeId, final Iterable inheritedSub, final Collection inheritedOne, - final Collection nodes) { + final Collection nodes) { this.nodeId = Preconditions.checkNotNull(nodeId); this.nodes = Preconditions.checkNotNull(nodes); this.inheritedSub = Preconditions.checkNotNull(inheritedSub); @@ -69,7 +69,7 @@ final class ResolveDataChangeState { final Map, Builder> sub = new HashMap<>(); final Map, Builder> one = new HashMap<>(); final Map, Builder> base = new HashMap<>(); - for (Node n : nodes) { + for (ListenerNode n : nodes) { for (DataChangeListenerRegistration l : n.getListeners()) { final Builder b = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE); switch (l.getScope()) { @@ -105,7 +105,7 @@ final class ResolveDataChangeState { * @param root root node * @return */ - public static ResolveDataChangeState initial(final YangInstanceIdentifier rootId, final Node root) { + public static ResolveDataChangeState initial(final YangInstanceIdentifier rootId, final ListenerNode root) { return new ResolveDataChangeState(rootId, Collections.emptyList(), Collections.emptyList(), Collections.singletonList(root)); } @@ -257,13 +257,13 @@ final class ResolveDataChangeState { LOG.trace("Collected events {}", map); } - private static Collection getListenerChildrenWildcarded(final Collection parentNodes, + private static Collection getListenerChildrenWildcarded(final Collection parentNodes, final PathArgument child) { if (parentNodes.isEmpty()) { return Collections.emptyList(); } - final List result = new ArrayList<>(); + final List result = new ArrayList<>(); if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) { NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType()); addChildNodes(result, parentNodes, wildcardedIdentifier); @@ -272,9 +272,9 @@ final class ResolveDataChangeState { return result; } - private static void addChildNodes(final List result, final Collection parentNodes, final PathArgument childIdentifier) { - for (Node node : parentNodes) { - Optional child = node.getChild(childIdentifier); + private static void addChildNodes(final List result, final Collection parentNodes, final PathArgument childIdentifier) { + for (ListenerNode node : parentNodes) { + Optional child = node.getChild(childIdentifier); if (child.isPresent()) { result.add(child.get()); } diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedWriteTransaction.java b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedWriteTransaction.java index 60a23403b3..faddbae850 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedWriteTransaction.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/SnapshotBackedWriteTransaction.java @@ -8,7 +8,7 @@ package org.opendaylight.controller.md.sal.dom.store.impl; import static com.google.common.base.Preconditions.checkState; -import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerNode.java b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerNode.java new file mode 100644 index 0000000000..0aef1429c4 --- /dev/null +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerNode.java @@ -0,0 +1,106 @@ +/** + * 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.md.sal.dom.store.impl.tree; + +import com.google.common.base.Optional; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration; +import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.DataChangeListenerRegistrationImpl; +import org.opendaylight.yangtools.concepts.Identifiable; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is a single node within the listener tree. Note that the data returned from + * and instance of this class is guaranteed to have any relevance or consistency + * only as long as the {@link ListenerWalker} instance through which it is reached remains + * unclosed. + * + * @author Robert Varga + */ +public class ListenerNode implements StoreTreeNode, Identifiable { + + private static final Logger LOG = LoggerFactory.getLogger(ListenerNode.class); + + private final Collection> listeners = new ArrayList<>(); + private final Map children = new HashMap<>(); + private final PathArgument identifier; + private final Reference parent; + + ListenerNode(final ListenerNode parent, final PathArgument identifier) { + this.parent = new WeakReference<>(parent); + this.identifier = identifier; + } + + @Override + public PathArgument getIdentifier() { + return identifier; + } + + @Override + public Optional getChild(final PathArgument child) { + return Optional.fromNullable(children.get(child)); + } + + /** + * Return the list of current listeners. This collection is guaranteed + * to be immutable only while the walker, through which this node is + * reachable remains unclosed. + * + * @return the list of current listeners + */ + public Collection> getListeners() { + return listeners; + } + + ListenerNode ensureChild(final PathArgument child) { + ListenerNode potential = children.get(child); + if (potential == null) { + potential = new ListenerNode(this, child); + children.put(child, potential); + } + return potential; + } + + void addListener(final DataChangeListenerRegistration listener) { + listeners.add(listener); + LOG.debug("Listener {} registered", listener); + } + + void removeListener(final DataChangeListenerRegistrationImpl listener) { + listeners.remove(listener); + LOG.debug("Listener {} unregistered", listener); + + // We have been called with the write-lock held, so we can perform some cleanup. + removeThisIfUnused(); + } + + private void removeThisIfUnused() { + final ListenerNode p = parent.get(); + if (p != null && listeners.isEmpty() && children.isEmpty()) { + p.removeChild(identifier); + } + } + + private void removeChild(final PathArgument arg) { + children.remove(arg); + removeThisIfUnused(); + } + + @Override + public String toString() { + return "Node [identifier=" + identifier + ", listeners=" + listeners.size() + ", children=" + children.size() + "]"; + } +} diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerTree.java b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerTree.java index ac7a318187..dcff6439d6 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerTree.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerTree.java @@ -7,41 +7,29 @@ */ package org.opendaylight.controller.md.sal.dom.store.impl.tree; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; - -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import javax.annotation.concurrent.GuardedBy; - import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener; import org.opendaylight.controller.md.sal.dom.store.impl.DataChangeListenerRegistration; import org.opendaylight.yangtools.concepts.AbstractListenerRegistration; -import org.opendaylight.yangtools.concepts.Identifiable; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A set of listeners organized as a tree by node to which they listen. This class * allows for efficient lookup of listeners when we walk the DataTreeCandidate. + * + * @author Robert Varga */ public final class ListenerTree { private static final Logger LOG = LoggerFactory.getLogger(ListenerTree.class); private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true); - private final Node rootNode = new Node(null, null); + private final ListenerNode rootNode = new ListenerNode(null, null); private ListenerTree() { // Private to disallow direct instantiation @@ -71,12 +59,12 @@ public final class ListenerTree { rwLock.writeLock().lock(); try { - Node walkNode = rootNode; + ListenerNode walkNode = rootNode; for (final PathArgument arg : path.getPathArguments()) { walkNode = walkNode.ensureChild(arg); } - final Node node = walkNode; + final ListenerNode node = walkNode; DataChangeListenerRegistration reg = new DataChangeListenerRegistrationImpl(listener) { @Override public DataChangeScope getScope() { @@ -128,7 +116,7 @@ public final class ListenerTree { * * @return A walker instance. */ - public Walker getWalker() { + public ListenerWalker getWalker() { /* * TODO: The only current user of this method is local to the datastore. * Since this class represents a read-lock, losing a reference to @@ -137,127 +125,12 @@ public final class ListenerTree { * external user exist, make the Walker a phantom reference, which * will cleanup the lock if not told to do so. */ - final Walker ret = new Walker(rwLock.readLock(), rootNode); + final ListenerWalker ret = new ListenerWalker(rwLock.readLock(), rootNode); rwLock.readLock().lock(); return ret; } - /** - * A walking context, pretty much equivalent to an iterator, but it - * exposes the underlying tree structure. - */ - /* - * FIXME: BUG-1511: split this class out as ListenerWalker. - */ - public static final class Walker implements AutoCloseable { - private final Lock lock; - private final Node node; - - @GuardedBy("this") - private boolean valid = true; - - private Walker(final Lock lock, final Node node) { - this.lock = Preconditions.checkNotNull(lock); - this.node = Preconditions.checkNotNull(node); - } - - public Node getRootNode() { - return node; - } - - @Override - public synchronized void close() { - if (valid) { - lock.unlock(); - valid = false; - } - } - } - - /** - * This is a single node within the listener tree. Note that the data returned from - * and instance of this class is guaranteed to have any relevance or consistency - * only as long as the {@link org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker} instance through which it is reached remains - * unclosed. - */ - /* - * FIXME: BUG-1511: split this class out as ListenerNode. - */ - public static final class Node implements StoreTreeNode, Identifiable { - private final Collection> listeners = new ArrayList<>(); - private final Map children = new HashMap<>(); - private final PathArgument identifier; - private final Reference parent; - - private Node(final Node parent, final PathArgument identifier) { - this.parent = new WeakReference<>(parent); - this.identifier = identifier; - } - - @Override - public PathArgument getIdentifier() { - return identifier; - } - - @Override - public Optional getChild(final PathArgument child) { - return Optional.fromNullable(children.get(child)); - } - - /** - * Return the list of current listeners. This collection is guaranteed - * to be immutable only while the walker, through which this node is - * reachable remains unclosed. - * - * @return the list of current listeners - */ - public Collection> getListeners() { - return listeners; - } - - private Node ensureChild(final PathArgument child) { - Node potential = children.get(child); - if (potential == null) { - potential = new Node(this, child); - children.put(child, potential); - } - return potential; - } - - private void addListener(final DataChangeListenerRegistration listener) { - listeners.add(listener); - LOG.debug("Listener {} registered", listener); - } - - private void removeListener(final DataChangeListenerRegistrationImpl listener) { - listeners.remove(listener); - LOG.debug("Listener {} unregistered", listener); - - // We have been called with the write-lock held, so we can perform some cleanup. - removeThisIfUnused(); - } - - private void removeThisIfUnused() { - final Node p = parent.get(); - if (p != null && listeners.isEmpty() && children.isEmpty()) { - p.removeChild(identifier); - } - } - - private void removeChild(final PathArgument arg) { - children.remove(arg); - removeThisIfUnused(); - } - - @Override - public String toString() { - return "Node [identifier=" + identifier + ", listeners=" + listeners.size() + ", children=" + children.size() + "]"; - } - - - } - - private abstract static class DataChangeListenerRegistrationImpl>> extends AbstractListenerRegistration // + abstract static class DataChangeListenerRegistrationImpl>> extends AbstractListenerRegistration // implements DataChangeListenerRegistration { public DataChangeListenerRegistrationImpl(final T listener) { super(listener); diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerWalker.java b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerWalker.java new file mode 100644 index 0000000000..0c297a2e2b --- /dev/null +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerWalker.java @@ -0,0 +1,44 @@ +/** + * 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.md.sal.dom.store.impl.tree; + +import com.google.common.base.Preconditions; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.locks.Lock; + +/** + * A walking context, pretty much equivalent to an iterator, but it + * exposes the underlying tree structure. + * + * @author Robert Varga + */ +public class ListenerWalker implements AutoCloseable { + private static final AtomicIntegerFieldUpdater CLOSED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ListenerWalker.class, "closed"); + private final Lock lock; + private final ListenerNode node; + + // Used via CLOSED_UPDATER + @SuppressWarnings("unused") + private volatile int closed = 0; + + ListenerWalker(final Lock lock, final ListenerNode node) { + this.lock = Preconditions.checkNotNull(lock); + this.node = Preconditions.checkNotNull(node); + } + + public ListenerNode getRootNode() { + return node; + } + + @Override + public void close() { + if (CLOSED_UPDATER.compareAndSet(this, 0, 1)) { + lock.unlock(); + } + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDataStoreTest.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDataStoreTest.java index 4720f4b4b9..9de4892d91 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDataStoreTest.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDataStoreTest.java @@ -45,7 +45,7 @@ public class InMemoryDataStoreTest { @Before public void setupStore() { - domStore = new InMemoryDOMDataStore("TEST", MoreExecutors.sameThreadExecutor()); + domStore = new InMemoryDOMDataStore("TEST", MoreExecutors.newDirectExecutorService()); schemaContext = TestModel.createTestContext(); domStore.onGlobalContextUpdated(schemaContext); } diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaUpdateForTransactionTest.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaUpdateForTransactionTest.java index 15e5f716f6..e7af4dffae 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaUpdateForTransactionTest.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/SchemaUpdateForTransactionTest.java @@ -31,7 +31,7 @@ public class SchemaUpdateForTransactionTest { @Before public void setupStore() { - domStore = new InMemoryDOMDataStore("TEST", MoreExecutors.sameThreadExecutor()); + domStore = new InMemoryDOMDataStore("TEST", MoreExecutors.newDirectExecutorService()); loadSchemas(RockTheHouseInput.class); } diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestDCLExecutorService.java b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestDCLExecutorService.java index f6e6461bf5..a01933c295 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestDCLExecutorService.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/test/java/org/opendaylight/controller/md/sal/dom/store/impl/TestDCLExecutorService.java @@ -8,10 +8,9 @@ package org.opendaylight.controller.md.sal.dom.store.impl; -import java.util.concurrent.ExecutorService; - import com.google.common.util.concurrent.ForwardingExecutorService; import com.google.common.util.concurrent.MoreExecutors; +import java.util.concurrent.ExecutorService; /** * A forwarding Executor used by unit tests for DataChangeListener notifications @@ -21,13 +20,13 @@ import com.google.common.util.concurrent.MoreExecutors; public class TestDCLExecutorService extends ForwardingExecutorService { // Start with a same thread executor to avoid timing issues during test setup. - private volatile ExecutorService currentExecutor = MoreExecutors.sameThreadExecutor(); + private volatile ExecutorService currentExecutor = MoreExecutors.newDirectExecutorService(); // The real executor to use when test setup is complete. private final ExecutorService postSetupExecutor; - public TestDCLExecutorService( ExecutorService postSetupExecutor ) { + public TestDCLExecutorService( final ExecutorService postSetupExecutor ) { this.postSetupExecutor = postSetupExecutor; } diff --git a/opendaylight/md-sal/sal-netconf-connector/pom.xml b/opendaylight/md-sal/sal-netconf-connector/pom.xml index c8836d1b88..61c83a68de 100644 --- a/opendaylight/md-sal/sal-netconf-connector/pom.xml +++ b/opendaylight/md-sal/sal-netconf-connector/pom.xml @@ -33,6 +33,10 @@ org.opendaylight.controller ietf-netconf-monitoring + + org.opendaylight.controller + ietf-netconf-notifications + org.opendaylight.controller netconf-client @@ -61,6 +65,10 @@ org.opendaylight.controller.model model-inventory + + org.opendaylight.yangtools.model + ietf-topology + org.opendaylight.controller sal-broker-impl diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java index 97e294016d..44b2435da2 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java @@ -9,6 +9,7 @@ package org.opendaylight.controller.config.yang.md.sal.connector.netconf; import static org.opendaylight.controller.config.api.JmxAttributeValidationException.checkCondition; import static org.opendaylight.controller.config.api.JmxAttributeValidationException.checkNotNull; + import com.google.common.base.Optional; import io.netty.util.concurrent.EventExecutor; import java.math.BigDecimal; @@ -26,7 +27,7 @@ import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.controller.sal.connect.netconf.NetconfDevice; import org.opendaylight.controller.sal.connect.netconf.NetconfStateSchemas; import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceSalFacade; import org.opendaylight.controller.sal.connect.netconf.schema.mapping.NetconfMessageTransformer; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; @@ -50,7 +51,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co private static final Logger logger = LoggerFactory.getLogger(NetconfConnectorModule.class); private BundleContext bundleContext; - private Optional userCapabilities; + private Optional userCapabilities; private SchemaSourceRegistry schemaRegistry; private SchemaContextFactory schemaContextFactory; @@ -87,7 +88,6 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co } userCapabilities = getUserCapabilities(); - } private boolean isHostAddressPresent(final Host address) { @@ -97,34 +97,34 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co @Override public java.lang.AutoCloseable createInstance() { - final RemoteDeviceId id = new RemoteDeviceId(getIdentifier()); + final RemoteDeviceId id = new RemoteDeviceId(getIdentifier(), getSocketAddress()); final ExecutorService globalProcessingExecutor = getProcessingExecutorDependency().getExecutor(); final Broker domBroker = getDomRegistryDependency(); final BindingAwareBroker bindingBroker = getBindingRegistryDependency(); - final RemoteDeviceHandler salFacade + final RemoteDeviceHandler salFacade = new NetconfDeviceSalFacade(id, domBroker, bindingBroker, bundleContext, globalProcessingExecutor); final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry, schemaContextFactory, new NetconfStateSchemas.NetconfStateSchemasResolverImpl()); final NetconfDevice device = - new NetconfDevice(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, new NetconfMessageTransformer()); + new NetconfDevice(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, new NetconfMessageTransformer(), getReconnectOnChangedSchema()); final NetconfDeviceCommunicator listener = userCapabilities.isPresent() ? new NetconfDeviceCommunicator(id, device, userCapabilities.get()) : new NetconfDeviceCommunicator(id, device); final NetconfReconnectingClientConfiguration clientConfig = getClientConfig(listener); - final NetconfClientDispatcher dispatcher = getClientDispatcherDependency(); + listener.initializeRemoteConnection(dispatcher, clientConfig); - return new MyAutoCloseable(listener, salFacade); + return new SalConnectorCloseable(listener, salFacade); } - private Optional getUserCapabilities() { + private Optional getUserCapabilities() { if(getYangModuleCapabilities() == null) { return Optional.absent(); } @@ -134,7 +134,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co return Optional.absent(); } - final NetconfSessionCapabilities parsedOverrideCapabilities = NetconfSessionCapabilities.fromStrings(capabilities); + final NetconfSessionPreferences parsedOverrideCapabilities = NetconfSessionPreferences.fromStrings(capabilities); JmxAttributeValidationException.checkCondition( parsedOverrideCapabilities.getNonModuleCaps().isEmpty(), "Capabilities to override can only contain module based capabilities, non-module capabilities will be retrieved from the device," + @@ -152,7 +152,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co final InetSocketAddress socketAddress = getSocketAddress(); final long clientConnectionTimeoutMillis = getConnectionTimeoutMillis(); - final ReconnectStrategyFactory sf = new MyReconnectStrategyFactory( + final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory( getEventExecutorDependency(), getMaxConnectionAttempts(), getBetweenAttemptsTimeoutMillis(), getSleepFactor()); final ReconnectStrategy strategy = sf.createReconnectStrategy(); @@ -160,21 +160,21 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co .withAddress(socketAddress) .withConnectionTimeoutMillis(clientConnectionTimeoutMillis) .withReconnectStrategy(strategy) - .withSessionListener(listener) .withAuthHandler(new LoginPassword(getUsername(),getPassword())) .withProtocol(getTcpOnly() ? NetconfClientConfiguration.NetconfClientProtocol.TCP : NetconfClientConfiguration.NetconfClientProtocol.SSH) .withConnectStrategyFactory(sf) + .withSessionListener(listener) .build(); } - private static final class MyAutoCloseable implements AutoCloseable { - private final RemoteDeviceHandler salFacade; + private static final class SalConnectorCloseable implements AutoCloseable { + private final RemoteDeviceHandler salFacade; private final NetconfDeviceCommunicator listener; - public MyAutoCloseable(final NetconfDeviceCommunicator listener, - final RemoteDeviceHandler salFacade) { + public SalConnectorCloseable(final NetconfDeviceCommunicator listener, + final RemoteDeviceHandler salFacade) { this.listener = listener; this.salFacade = salFacade; } @@ -186,13 +186,13 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co } } - private static final class MyReconnectStrategyFactory implements ReconnectStrategyFactory { + private static final class TimedReconnectStrategyFactory implements ReconnectStrategyFactory { private final Long connectionAttempts; private final EventExecutor executor; private final double sleepFactor; private final int minSleep; - MyReconnectStrategyFactory(final EventExecutor executor, final Long maxConnectionAttempts, final int minSleep, final BigDecimal sleepFactor) { + TimedReconnectStrategyFactory(final EventExecutor executor, final Long maxConnectionAttempts, final int minSleep, final BigDecimal sleepFactor) { if (maxConnectionAttempts != null && maxConnectionAttempts > 0) { connectionAttempts = maxConnectionAttempts; } else { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java index e0d24331a7..ca12e596e6 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java @@ -10,11 +10,13 @@ package org.opendaylight.controller.sal.connect.api; /** * */ -public interface RemoteDevice { +public interface RemoteDevice> { - void onRemoteSessionUp(PREF remoteSessionCapabilities, RemoteDeviceCommunicator listener); + void onRemoteSessionUp(PREF remoteSessionCapabilities, LISTENER listener); void onRemoteSessionDown(); + void onRemoteSessionFailed(Throwable throwable); + void onNotification(M notification); } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceHandler.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceHandler.java index 269c4af82f..c5a0ae2544 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceHandler.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDeviceHandler.java @@ -18,6 +18,8 @@ public interface RemoteDeviceHandler extends AutoCloseable { void onDeviceDisconnected(); + void onDeviceFailed(Throwable throwable); + void onNotification(CompositeNode domNotification); void close(); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java index 31779a7817..281f82ba2a 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -30,11 +31,18 @@ import org.opendaylight.controller.sal.connect.api.MessageTransformer; import org.opendaylight.controller.sal.connect.api.RemoteDevice; import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceRpc; import org.opendaylight.controller.sal.connect.netconf.schema.NetconfRemoteSchemaYangSourceProvider; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.unavailable.capabilities.UnavailableCapability; 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.SchemaContext; import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException; import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory; @@ -51,7 +59,7 @@ import org.slf4j.LoggerFactory; /** * This is a mediator between NetconfDeviceCommunicator and NetconfDeviceSalFacade */ -public final class NetconfDevice implements RemoteDevice { +public final class NetconfDevice implements RemoteDevice { private static final Logger logger = LoggerFactory.getLogger(NetconfDevice.class); @@ -63,9 +71,10 @@ public final class NetconfDevice implements RemoteDevice salFacade; + private final RemoteDeviceHandler salFacade; private final ListeningExecutorService processingExecutor; private final SchemaSourceRegistry schemaRegistry; private final MessageTransformer messageTransformer; @@ -73,9 +82,16 @@ public final class NetconfDevice implements RemoteDevice> sourceRegistrations = Lists.newArrayList(); - public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler salFacade, + public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler salFacade, final ExecutorService globalProcessingExecutor, final MessageTransformer messageTransformer) { + this(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, messageTransformer, false); + } + + // FIXME reduce parameters + public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler salFacade, + final ExecutorService globalProcessingExecutor, final MessageTransformer messageTransformer, final boolean reconnectOnSchemasChange) { this.id = id; + this.reconnectOnSchemasChange = reconnectOnSchemasChange; this.schemaRegistry = schemaResourcesDTO.getSchemaRegistry(); this.messageTransformer = messageTransformer; this.schemaContextFactory = schemaResourcesDTO.getSchemaContextFactory(); @@ -86,8 +102,8 @@ public final class NetconfDevice implements RemoteDevice listener) { + public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities, + final NetconfDeviceCommunicator listener) { // SchemaContext setup has to be performed in a dedicated thread since // we are in a netty thread in this method // Yang models are being downloaded in this method and it would cause a @@ -100,6 +116,10 @@ public final class NetconfDevice implements RemoteDevice sourceResolverFuture = processingExecutor.submit(task); + if(shouldListenOnSchemaChange(remoteSessionCapabilities)) { + registerToBaseNetconfStream(deviceRpc, listener); + } + final FutureCallback resolvedSourceCallback = new FutureCallback() { @Override public void onSuccess(final DeviceSources result) { @@ -119,14 +139,52 @@ public final class NetconfDevice implements RemoteDevice> rpcResultListenableFuture = + deviceRpc.invokeRpc(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME, NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_CONTENT); + + final NotificationHandler.NotificationFilter filter = new NotificationHandler.NotificationFilter() { + @Override + public Optional filterNotification(final CompositeNode notification) { + if (isCapabilityChanged(notification)) { + logger.info("{}: Schemas change detected, reconnecting", id); + // Only disconnect is enough, the reconnecting nature of the connector will take care of reconnecting + listener.disconnect(); + return Optional.absent(); + } + return Optional.of(notification); + } + + private boolean isCapabilityChanged(final CompositeNode notification) { + return notification.getNodeType().equals(NetconfCapabilityChange.QNAME); + } + }; + + Futures.addCallback(rpcResultListenableFuture, new FutureCallback>() { + @Override + public void onSuccess(final RpcResult result) { + notificationHandler.addNotificationFilter(filter); + } + + @Override + public void onFailure(final Throwable t) { + logger.warn("Unable to subscribe to base notification stream. Schemas will not be reloaded on the fly", t); + } + }); + } + + private boolean shouldListenOnSchemaChange(final NetconfSessionPreferences remoteSessionCapabilities) { + return remoteSessionCapabilities.isNotificationsSupported() && reconnectOnSchemasChange; } - private void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionCapabilities remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc) { + private void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc) { updateMessageTransformer(result); salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc); notificationHandler.onRemoteSchemaUp(); - logger.debug("{}: Initialization in sal successful", id); logger.info("{}: Netconf connector initialized successfully", id); } @@ -146,7 +204,6 @@ public final class NetconfDevice implements RemoteDevice sourceRegistration : sourceRegistrations) { sourceRegistration.close(); @@ -173,6 +232,11 @@ public final class NetconfDevice implements RemoteDevice { private final NetconfDeviceRpc deviceRpc; - private final NetconfSessionCapabilities remoteSessionCapabilities; + private final NetconfSessionPreferences remoteSessionCapabilities; private final RemoteDeviceId id; private final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver; - public DeviceSourcesResolver(final NetconfDeviceRpc deviceRpc, final NetconfSessionCapabilities remoteSessionCapabilities, final RemoteDeviceId id, final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver) { + public DeviceSourcesResolver(final NetconfDeviceRpc deviceRpc, final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id, final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver) { this.deviceRpc = deviceRpc; this.remoteSessionCapabilities = remoteSessionCapabilities; this.id = id; @@ -252,6 +316,7 @@ public final class NetconfDevice implements RemoteDevice listener; + private NetconfDeviceCapabilities capabilities; - public RecursiveSchemaSetup(final DeviceSources deviceSources, final NetconfSessionCapabilities remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc, final RemoteDeviceCommunicator listener) { + public RecursiveSchemaSetup(final DeviceSources deviceSources, final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc, final RemoteDeviceCommunicator listener) { this.deviceSources = deviceSources; this.remoteSessionCapabilities = remoteSessionCapabilities; this.deviceRpc = deviceRpc; this.listener = listener; + this.capabilities = remoteSessionCapabilities.getNetconfDeviceCapabilities(); } @Override @@ -306,6 +373,7 @@ public final class NetconfDevice implements RemoteDevice requiredSources) { logger.trace("{}: Trying to build schema context from {}", id, requiredSources); @@ -322,6 +390,9 @@ public final class NetconfDevice implements RemoteDevice filteredQNames = Sets.difference(remoteSessionCapabilities.getModuleBasedCaps(), capabilities.getUnresolvedCapabilites().keySet()); + capabilities.addCapabilities(filteredQNames); + capabilities.addNonModuleBasedCapabilities(remoteSessionCapabilities.getNonModuleCaps()); handleSalInitializationSuccess(result, remoteSessionCapabilities, deviceRpc); } @@ -331,12 +402,15 @@ public final class NetconfDevice implements RemoteDevice unresolvedSources = resolutionException.getUnsatisfiedImports().keySet(); + capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(unresolvedSources), UnavailableCapability.FailureReason.UnableToResolve); logger.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only", id, resolutionException.getUnsatisfiedImports()); setUpSchema(resolutionException.getResolvedSources()); // unknown error, fail @@ -355,5 +429,29 @@ public final class NetconfDevice implements RemoteDevice getQNameFromSourceIdentifiers(Collection identifiers) { + Collection qNames = new HashSet<>(); + for (SourceIdentifier source : identifiers) { + Optional qname = getQNameFromSourceIdentifier(source); + if (qname.isPresent()) { + qNames.add(qname.get()); + } + } + if (qNames.isEmpty()) { + logger.debug("Unable to map any source identfiers to a capability reported by device : " + identifiers); + } + return qNames; + } + + private Optional getQNameFromSourceIdentifier(SourceIdentifier identifier) { + for (QName qname : remoteSessionCapabilities.getModuleBasedCaps()) { + if (qname.getLocalName().equals(identifier.getName()) + && qname.getFormattedRevision().equals(identifier.getRevision())) { + return Optional.of(qname); + } + } + throw new IllegalArgumentException("Unable to map identifier to a devices reported capability: " + identifier); + } } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfStateSchemas.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfStateSchemas.java index d758073a8e..68c1a5c6a8 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfStateSchemas.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfStateSchemas.java @@ -11,7 +11,7 @@ import java.net.URI; import java.util.Collections; import java.util.Set; import java.util.concurrent.ExecutionException; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceRpc; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; @@ -40,7 +40,7 @@ public final class NetconfStateSchemas { * Factory for NetconfStateSchemas */ public interface NetconfStateSchemasResolver { - NetconfStateSchemas resolve(final NetconfDeviceRpc deviceRpc, final NetconfSessionCapabilities remoteSessionCapabilities, final RemoteDeviceId id); + NetconfStateSchemas resolve(final NetconfDeviceRpc deviceRpc, final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id); } /** @@ -49,7 +49,7 @@ public final class NetconfStateSchemas { public static final class NetconfStateSchemasResolverImpl implements NetconfStateSchemasResolver { @Override - public NetconfStateSchemas resolve(final NetconfDeviceRpc deviceRpc, final NetconfSessionCapabilities remoteSessionCapabilities, final RemoteDeviceId id) { + public NetconfStateSchemas resolve(final NetconfDeviceRpc deviceRpc, final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id) { return NetconfStateSchemas.create(deviceRpc, remoteSessionCapabilities, id); } } @@ -91,7 +91,7 @@ public final class NetconfStateSchemas { /** * Issue get request to remote device and parse response to find all schemas under netconf-state/schemas */ - private static NetconfStateSchemas create(final NetconfDeviceRpc deviceRpc, final NetconfSessionCapabilities remoteSessionCapabilities, final RemoteDeviceId id) { + private static NetconfStateSchemas create(final NetconfDeviceRpc deviceRpc, final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id) { if(remoteSessionCapabilities.isMonitoringSupported() == false) { logger.warn("{}: Netconf monitoring not supported on device, cannot detect provided schemas"); return EMPTY; diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NotificationHandler.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NotificationHandler.java index cc8960fb4f..bc3326e1ae 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NotificationHandler.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NotificationHandler.java @@ -7,6 +7,7 @@ */ package org.opendaylight.controller.sal.connect.netconf; +import com.google.common.base.Optional; import com.google.common.base.Preconditions; import java.util.LinkedList; import java.util.List; @@ -31,6 +32,7 @@ final class NotificationHandler { private final MessageTransformer messageTransformer; private final RemoteDeviceId id; private boolean passNotifications = false; + private NotificationFilter filter; NotificationHandler(final RemoteDeviceHandler salFacade, final MessageTransformer messageTransformer, final RemoteDeviceId id) { this.salFacade = Preconditions.checkNotNull(salFacade); @@ -40,7 +42,7 @@ final class NotificationHandler { synchronized void handleNotification(final NetconfMessage notification) { if(passNotifications) { - passNotification(messageTransformer.toNotification(notification)); + passNotification(transformNotification(notification)); } else { queueNotification(notification); } @@ -53,12 +55,18 @@ final class NotificationHandler { passNotifications = true; for (final NetconfMessage cachedNotification : queue) { - passNotification(messageTransformer.toNotification(cachedNotification)); + passNotification(transformNotification(cachedNotification)); } queue.clear(); } + private CompositeNode transformNotification(final NetconfMessage cachedNotification) { + final CompositeNode parsedNotification = messageTransformer.toNotification(cachedNotification); + Preconditions.checkNotNull(parsedNotification, "%s: Unable to parse received notification: %s", id, cachedNotification); + return parsedNotification; + } + private void queueNotification(final NetconfMessage notification) { Preconditions.checkState(passNotifications == false); @@ -70,9 +78,25 @@ final class NotificationHandler { queue.add(notification); } - private void passNotification(final CompositeNode parsedNotification) { + private synchronized void passNotification(final CompositeNode parsedNotification) { logger.debug("{}: Forwarding notification {}", id, parsedNotification); - Preconditions.checkNotNull(parsedNotification); - salFacade.onNotification(parsedNotification); + + if(filter == null || filter.filterNotification(parsedNotification).isPresent()) { + salFacade.onNotification(parsedNotification); + } + } + + synchronized void addNotificationFilter(final NotificationFilter filter) { + this.filter = filter; + } + + synchronized void onRemoteSchemaDown() { + queue.clear(); + passNotifications = false; + } + + static interface NotificationFilter { + + Optional filterNotification(CompositeNode notification); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCapabilities.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCapabilities.java new file mode 100644 index 0000000000..8f30a5c63a --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCapabilities.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.sal.connect.netconf.listener; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.unavailable.capabilities.UnavailableCapability.FailureReason; +import org.opendaylight.yangtools.yang.common.QName; + +public final class NetconfDeviceCapabilities { + private final Map unresolvedCapabilites; + private final Set resolvedCapabilities; + + private final Set nonModuleBasedCapabilities; + + public NetconfDeviceCapabilities() { + this.unresolvedCapabilites = new HashMap<>(); + this.resolvedCapabilities = new HashSet<>(); + this.nonModuleBasedCapabilities = new HashSet<>(); + } + + public void addUnresolvedCapability(QName source, FailureReason reason) { + unresolvedCapabilites.put(source, reason); + } + + public void addUnresolvedCapabilities(Collection capabilities, FailureReason reason) { + for (QName s : capabilities) { + unresolvedCapabilites.put(s, reason); + } + } + + public void addCapabilities(Collection availableSchemas) { + resolvedCapabilities.addAll(availableSchemas); + } + + public void addNonModuleBasedCapabilities(Collection nonModuleCapabilities) { + this.nonModuleBasedCapabilities.addAll(nonModuleCapabilities); + } + + public Set getNonModuleBasedCapabilities() { + return nonModuleBasedCapabilities; + } + + public Map getUnresolvedCapabilites() { + return unresolvedCapabilites; + } + + public Set getResolvedCapabilities() { + return resolvedCapabilities; + } + +} 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 aadb911f45..8553820b40 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 @@ -14,6 +14,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; +import io.netty.util.concurrent.GenericFutureListener; import java.util.ArrayDeque; import java.util.Iterator; import java.util.List; @@ -46,8 +47,8 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceCommunicator.class); - private final RemoteDevice remoteDevice; - private final Optional overrideNetconfCapabilities; + private final RemoteDevice remoteDevice; + private final Optional overrideNetconfCapabilities; private final RemoteDeviceId id; private final Lock sessionLock = new ReentrantLock(); @@ -56,18 +57,18 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, private NetconfClientSession session; private Future initFuture; - public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, - final NetconfSessionCapabilities netconfSessionCapabilities) { - this(id, remoteDevice, Optional.of(netconfSessionCapabilities)); + public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, + final NetconfSessionPreferences NetconfSessionPreferences) { + this(id, remoteDevice, Optional.of(NetconfSessionPreferences)); } public NetconfDeviceCommunicator(final RemoteDeviceId id, - final RemoteDevice remoteDevice) { - this(id, remoteDevice, Optional.absent()); + final RemoteDevice remoteDevice) { + this(id, remoteDevice, Optional.absent()); } - private NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, - final Optional overrideNetconfCapabilities) { + private NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, + final Optional overrideNetconfCapabilities) { this.id = id; this.remoteDevice = remoteDevice; this.overrideNetconfCapabilities = overrideNetconfCapabilities; @@ -80,28 +81,47 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, logger.debug("{}: Session established", id); this.session = session; - NetconfSessionCapabilities netconfSessionCapabilities = - NetconfSessionCapabilities.fromNetconfSession(session); - logger.trace("{}: Session advertised capabilities: {}", id, netconfSessionCapabilities); + NetconfSessionPreferences netconfSessionPreferences = + NetconfSessionPreferences.fromNetconfSession(session); + logger.trace("{}: Session advertised capabilities: {}", id, netconfSessionPreferences); if(overrideNetconfCapabilities.isPresent()) { - netconfSessionCapabilities = netconfSessionCapabilities.replaceModuleCaps(overrideNetconfCapabilities.get()); - logger.debug("{}: Session capabilities overridden, capabilities that will be used: {}", id, netconfSessionCapabilities); + netconfSessionPreferences = netconfSessionPreferences.replaceModuleCaps(overrideNetconfCapabilities.get()); + logger.debug("{}: Session capabilities overridden, capabilities that will be used: {}", id, netconfSessionPreferences); } - remoteDevice.onRemoteSessionUp(netconfSessionCapabilities, this); + remoteDevice.onRemoteSessionUp(netconfSessionPreferences, this); } finally { sessionLock.unlock(); } } - public void initializeRemoteConnection(final NetconfClientDispatcher dispatch, - final NetconfClientConfiguration config) { + public void initializeRemoteConnection(final NetconfClientDispatcher dispatcher, final NetconfClientConfiguration config) { + // TODO 2313 extract listener from configuration if(config instanceof NetconfReconnectingClientConfiguration) { - initFuture = dispatch.createReconnectingClient((NetconfReconnectingClientConfiguration) config); + initFuture = dispatcher.createReconnectingClient((NetconfReconnectingClientConfiguration) config); } else { - initFuture = dispatch.createClient(config); + initFuture = dispatcher.createClient(config); + } + + + initFuture.addListener(new GenericFutureListener>(){ + + @Override + public void operationComplete(Future future) throws Exception { + if (!future.isSuccess()) { + logger.debug("{}: Connection failed", id, future.cause()); + NetconfDeviceCommunicator.this.remoteDevice.onRemoteSessionFailed(future.cause()); + } + } + }); + + } + + public void disconnect() { + if(session != null) { + session.close(); } } @@ -146,18 +166,14 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, } } - private RpcResult createSessionDownRpcResult() - { + private RpcResult createSessionDownRpcResult() { return createErrorRpcResult( RpcError.ErrorType.TRANSPORT, String.format( "The netconf session to %1$s is disconnected", id.getName() ) ); } - private RpcResult createErrorRpcResult( RpcError.ErrorType errorType, String message ) - { + private RpcResult createErrorRpcResult( RpcError.ErrorType errorType, String message ) { return RpcResultBuilder.failed() - .withError( errorType, NetconfDocumentedException.ErrorTag.operation_failed.getTagValue(), - message ) - .build(); + .withError(errorType, NetconfDocumentedException.ErrorTag.operation_failed.getTagValue(), message).build(); } @Override @@ -182,6 +198,7 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, if(session != null) { session.close(); } + tearDown(id + ": Netconf session closed"); } @@ -220,14 +237,12 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, logger.debug("{}: Message received {}", id, message); if(logger.isTraceEnabled()) { - logger.trace( "{}: Matched request: {} to response: {}", id, - msgToS( request.request ), msgToS( message ) ); + logger.trace( "{}: Matched request: {} to response: {}", id, msgToS( request.request ), msgToS( message ) ); } try { NetconfMessageTransformUtil.checkValidReply( request.request, message ); - } - catch (final NetconfDocumentedException e) { + } catch (final NetconfDocumentedException e) { logger.warn( "{}: Invalid request-reply match, reply message contains different message-id, request: {}, response: {}", id, msgToS( request.request ), msgToS( message ), e ); @@ -238,8 +253,7 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, try { NetconfMessageTransformUtil.checkSuccessReply(message); - } - catch(final NetconfDocumentedException e) { + } catch(final NetconfDocumentedException e) { logger.warn( "{}: Error reply from remote device, request: {}, response: {}", id, msgToS( request.request ), msgToS( message ), e ); @@ -257,13 +271,11 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, } @Override - public ListenableFuture> sendRequest( - final NetconfMessage message, final QName rpc) { + public ListenableFuture> sendRequest(final NetconfMessage message, final QName rpc) { sessionLock.lock(); try { return sendRequestWithLock( message, rpc ); - } - finally { + } finally { sessionLock.unlock(); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferences.java similarity index 86% rename from opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java rename to opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferences.java index d5b3778b4f..89211ede77 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferences.java @@ -18,7 +18,7 @@ import org.opendaylight.yangtools.yang.common.QName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public final class NetconfSessionCapabilities { +public final class NetconfSessionPreferences { private static final class ParameterMatcher { private final Predicate predicate; @@ -45,7 +45,7 @@ public final class NetconfSessionCapabilities { } } - private static final Logger LOG = LoggerFactory.getLogger(NetconfSessionCapabilities.class); + private static final Logger LOG = LoggerFactory.getLogger(NetconfSessionPreferences.class); private static final ParameterMatcher MODULE_PARAM = new ParameterMatcher("module="); private static final ParameterMatcher REVISION_PARAM = new ParameterMatcher("revision="); private static final ParameterMatcher BROKEN_REVISON_PARAM = new ParameterMatcher("amp;revision="); @@ -60,7 +60,7 @@ public final class NetconfSessionCapabilities { private final Set moduleBasedCaps; private final Set nonModuleCaps; - private NetconfSessionCapabilities(final Set nonModuleCaps, final Set moduleBasedCaps) { + private NetconfSessionPreferences(final Set nonModuleCaps, final Set moduleBasedCaps) { this.nonModuleCaps = Preconditions.checkNotNull(nonModuleCaps); this.moduleBasedCaps = Preconditions.checkNotNull(moduleBasedCaps); } @@ -105,22 +105,27 @@ public final class NetconfSessionCapabilities { return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_RUNNING_WRITABLE_URI.toString()); } + public boolean isNotificationsSupported() { + return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_NOTIFICATONS_URI.toString()) + || containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_NOTIFICATIONS); + } + public boolean isMonitoringSupported() { return containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING) || containsNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); } - public NetconfSessionCapabilities replaceModuleCaps(final NetconfSessionCapabilities netconfSessionModuleCapabilities) { + public NetconfSessionPreferences replaceModuleCaps(final NetconfSessionPreferences netconfSessionModuleCapabilities) { final Set moduleBasedCaps = Sets.newHashSet(netconfSessionModuleCapabilities.getModuleBasedCaps()); // Preserve monitoring module, since it indicates support for ietf-netconf-monitoring if(containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING)) { moduleBasedCaps.add(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING); } - return new NetconfSessionCapabilities(getNonModuleCaps(), moduleBasedCaps); + return new NetconfSessionPreferences(getNonModuleCaps(), moduleBasedCaps); } - public static NetconfSessionCapabilities fromNetconfSession(final NetconfClientSession session) { + public static NetconfSessionPreferences fromNetconfSession(final NetconfClientSession session) { return fromStrings(session.getServerCapabilities()); } @@ -132,7 +137,7 @@ public final class NetconfSessionCapabilities { return QName.cachedReference(QName.create(URI.create(namespace), null, moduleName).withoutRevision()); } - public static NetconfSessionCapabilities fromStrings(final Collection capabilities) { + public static NetconfSessionPreferences fromStrings(final Collection capabilities) { final Set moduleBasedCaps = new HashSet<>(); final Set nonModuleCaps = Sets.newHashSet(capabilities); @@ -176,7 +181,7 @@ public final class NetconfSessionCapabilities { addModuleQName(moduleBasedCaps, nonModuleCaps, capability, cachedQName(namespace, moduleName)); } - return new NetconfSessionCapabilities(ImmutableSet.copyOf(nonModuleCaps), ImmutableSet.copyOf(moduleBasedCaps)); + return new NetconfSessionPreferences(ImmutableSet.copyOf(nonModuleCaps), ImmutableSet.copyOf(moduleBasedCaps)); } @@ -184,4 +189,12 @@ public final class NetconfSessionCapabilities { moduleBasedCaps.add(qName); nonModuleCaps.remove(capability); } + + private NetconfDeviceCapabilities capabilities = new NetconfDeviceCapabilities(); + + public NetconfDeviceCapabilities getNetconfDeviceCapabilities() { + return capabilities; + } + + } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java index aa22e877a4..87ca11de87 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java @@ -17,7 +17,7 @@ import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction; import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.sal.tx.ReadOnlyTx; import org.opendaylight.controller.sal.connect.netconf.sal.tx.ReadWriteTx; import org.opendaylight.controller.sal.connect.netconf.sal.tx.WriteCandidateTx; @@ -33,10 +33,10 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; final class NetconfDeviceDataBroker implements DOMDataBroker { private final RemoteDeviceId id; private final NetconfBaseOps netconfOps; - private final NetconfSessionCapabilities netconfSessionPreferences; + private final NetconfSessionPreferences netconfSessionPreferences; private final DataNormalizer normalizer; - public NetconfDeviceDataBroker(final RemoteDeviceId id, final RpcImplementation rpc, final SchemaContext schemaContext, final NetconfSessionCapabilities netconfSessionPreferences) { + public NetconfDeviceDataBroker(final RemoteDeviceId id, final RpcImplementation rpc, final SchemaContext schemaContext, final NetconfSessionPreferences netconfSessionPreferences) { this.id = id; this.netconfOps = new NetconfBaseOps(rpc); this.netconfSessionPreferences = netconfSessionPreferences; diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDatastoreAdapter.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDatastoreAdapter.java index fc69a7e253..3715969b2b 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDatastoreAdapter.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDatastoreAdapter.java @@ -38,6 +38,7 @@ import org.slf4j.LoggerFactory; * * All data changes are submitted to an ExecutorService to avoid Thread blocking while sal is waiting for schema. */ +@Deprecated final class NetconfDeviceDatastoreAdapter implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceDatastoreAdapter.class); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalFacade.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalFacade.java index bdeb129d55..db8a238242 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalFacade.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalFacade.java @@ -16,7 +16,8 @@ import java.util.concurrent.ExecutorService; import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.controller.sal.core.api.Broker; import org.opendaylight.controller.sal.core.api.RpcImplementation; @@ -36,7 +37,7 @@ import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDeviceHandler { +public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDeviceHandler { private static final Logger logger= LoggerFactory.getLogger(NetconfDeviceSalFacade.class); @@ -63,7 +64,7 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice @Override public synchronized void onDeviceConnected(final SchemaContext schemaContext, - final NetconfSessionCapabilities netconfSessionPreferences, final RpcImplementation deviceRpc) { + final NetconfSessionPreferences netconfSessionPreferences, final RpcImplementation deviceRpc) { // TODO move SchemaAwareRpcBroker from sal-broker-impl, now we have depend on the whole sal-broker-impl final RpcProvisionRegistry rpcRegistry = new SchemaAwareRpcBroker(id.getPath().toString(), new SchemaContextProvider() { @@ -93,12 +94,23 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice salProvider.getMountInstance().onDeviceConnected(schemaContext, domBroker, rpcRegistry, notificationService); salProvider.getDatastoreAdapter().updateDeviceState(true, netconfSessionPreferences.getModuleBasedCaps()); + salProvider.getMountInstance().onTopologyDeviceConnected(schemaContext, domBroker, rpcRegistry, notificationService); + salProvider.getTopologyDatastoreAdapter().updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities()); } @Override public synchronized void onDeviceDisconnected() { salProvider.getDatastoreAdapter().updateDeviceState(false, Collections.emptySet()); + salProvider.getTopologyDatastoreAdapter().updateDeviceData(false, new NetconfDeviceCapabilities()); salProvider.getMountInstance().onDeviceDisconnected(); + salProvider.getMountInstance().onTopologyDeviceDisconnected(); + } + + @Override + public void onDeviceFailed(Throwable throwable) { + salProvider.getTopologyDatastoreAdapter().setDeviceAsFailed(throwable); + salProvider.getMountInstance().onDeviceDisconnected(); + salProvider.getMountInstance().onTopologyDeviceDisconnected(); } private void registerRpcsToSal(final SchemaContext schemaContext, final RpcProvisionRegistry rpcRegistry, final RpcImplementation deviceRpc) { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalProvider.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalProvider.java index 171f2f4b0b..dfae165d30 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalProvider.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceSalProvider.java @@ -37,6 +37,8 @@ final class NetconfDeviceSalProvider implements AutoCloseable, Provider, Binding private volatile NetconfDeviceDatastoreAdapter datastoreAdapter; private MountInstance mountInstance; + private volatile NetconfDeviceTopologyAdapter topologyDatastoreAdapter; + public NetconfDeviceSalProvider(final RemoteDeviceId deviceId, final ExecutorService executor) { this.id = deviceId; this.executor = executor; @@ -54,6 +56,12 @@ final class NetconfDeviceSalProvider implements AutoCloseable, Provider, Binding return datastoreAdapter; } + public NetconfDeviceTopologyAdapter getTopologyDatastoreAdapter() { + Preconditions.checkState(topologyDatastoreAdapter != null, + "%s: Sal provider %s was not initialized by sal. Cannot get topology datastore adapter", id); + return topologyDatastoreAdapter; + } + @Override public void onSessionInitiated(final Broker.ProviderSession session) { logger.debug("{}: (BI)Session with sal established {}", id, session); @@ -75,6 +83,8 @@ final class NetconfDeviceSalProvider implements AutoCloseable, Provider, Binding final DataBroker dataBroker = session.getSALService(DataBroker.class); datastoreAdapter = new NetconfDeviceDatastoreAdapter(id, dataBroker); + + topologyDatastoreAdapter = new NetconfDeviceTopologyAdapter(id, dataBroker); } public void close() throws Exception { @@ -90,11 +100,14 @@ final class NetconfDeviceSalProvider implements AutoCloseable, Provider, Binding private ObjectRegistration registration; private NotificationPublishService notificationSerivce; + private ObjectRegistration topologyRegistration; + MountInstance(final DOMMountPointService mountService, final RemoteDeviceId id) { this.mountService = Preconditions.checkNotNull(mountService); this.id = Preconditions.checkNotNull(id); } + @Deprecated synchronized void onDeviceConnected(final SchemaContext initialCtx, final DOMDataBroker broker, final RpcProvisionRegistry rpc, final NotificationPublishService notificationSerivce) { @@ -113,6 +126,7 @@ final class NetconfDeviceSalProvider implements AutoCloseable, Provider, Binding registration = mountBuilder.register(); } + @Deprecated synchronized void onDeviceDisconnected() { if(registration == null) { return; @@ -128,10 +142,44 @@ final class NetconfDeviceSalProvider implements AutoCloseable, Provider, Binding } } + synchronized void onTopologyDeviceConnected(final SchemaContext initialCtx, + final DOMDataBroker broker, final RpcProvisionRegistry rpc, + final NotificationPublishService notificationSerivce) { + + Preconditions.checkNotNull(mountService, "Closed"); + Preconditions.checkState(topologyRegistration == null, "Already initialized"); + + final DOMMountPointService.DOMMountPointBuilder mountBuilder = mountService.createMountPoint(id.getTopologyPath()); + mountBuilder.addInitialSchemaContext(initialCtx); + + mountBuilder.addService(DOMDataBroker.class, broker); + mountBuilder.addService(RpcProvisionRegistry.class, rpc); + this.notificationSerivce = notificationSerivce; + mountBuilder.addService(NotificationPublishService.class, notificationSerivce); + + topologyRegistration = mountBuilder.register(); + } + + synchronized void onTopologyDeviceDisconnected() { + if(topologyRegistration == null) { + return; + } + + try { + topologyRegistration.close(); + } catch (final Exception e) { + // Only log and ignore + logger.warn("Unable to unregister mount instance for {}. Ignoring exception", id.getTopologyPath(), e); + } finally { + topologyRegistration = null; + } + } + @Override synchronized public void close() throws Exception { if(registration != null) { onDeviceDisconnected(); + onTopologyDeviceDisconnected(); } mountService = null; } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceTopologyAdapter.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceTopologyAdapter.java new file mode 100644 index 0000000000..83664e440f --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceTopologyAdapter.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.sal.connect.netconf.sal; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCapabilities; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeFields.ConnectionStatus; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.AvailableCapabilitiesBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.UnavailableCapabilities; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.UnavailableCapabilitiesBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.unavailable.capabilities.UnavailableCapability; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.unavailable.capabilities.UnavailableCapability.FailureReason; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.unavailable.capabilities.UnavailableCapabilityBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.network.topology.topology.topology.types.TopologyNetconf; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopologyBuilder; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier; +import org.opendaylight.yangtools.yang.common.QName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class NetconfDeviceTopologyAdapter implements AutoCloseable { + + public static final Logger logger = LoggerFactory.getLogger(NetconfDeviceTopologyAdapter.class); + public static final Function, UnavailableCapability> UNAVAILABLE_CAPABILITY_TRANSFORMER = new Function, UnavailableCapability>() { + @Override + public UnavailableCapability apply(final Entry input) { + return new UnavailableCapabilityBuilder() + .setCapability(input.getKey().toString()) + .setFailureReason(input.getValue()).build(); + } + }; + public static final Function AVAILABLE_CAPABILITY_TRANSFORMER = new Function() { + @Override + public String apply(QName qName) { + return qName.toString(); + } + }; + + private final RemoteDeviceId id; + private final DataBroker dataService; + + private final InstanceIdentifier networkTopologyPath; + private final KeyedInstanceIdentifier topologyListPath; + private static final String UNKNOWN_REASON = "Unknown reason"; + + NetconfDeviceTopologyAdapter(final RemoteDeviceId id, final DataBroker dataService) { + this.id = id; + this.dataService = dataService; + + this.networkTopologyPath = InstanceIdentifier.builder(NetworkTopology.class).build(); + this.topologyListPath = networkTopologyPath.child(Topology.class, new TopologyKey(new TopologyId(TopologyNetconf.QNAME.getLocalName()))); + + initDeviceData(); + } + + private void initDeviceData() { + final WriteTransaction writeTx = dataService.newWriteOnlyTransaction(); + + createNetworkTopologyIfNotPresent(writeTx); + + final InstanceIdentifier path = id.getTopologyBindingPath(); + NodeBuilder nodeBuilder = getNodeIdBuilder(id); + NetconfNodeBuilder netconfNodeBuilder = new NetconfNodeBuilder(); + netconfNodeBuilder.setConnectionStatus(ConnectionStatus.Connecting); + netconfNodeBuilder.setHost(id.getHost()); + netconfNodeBuilder.setPort(new PortNumber(id.getAddress().getPort())); + nodeBuilder.addAugmentation(NetconfNode.class, netconfNodeBuilder.build()); + Node node = nodeBuilder.build(); + + logger.trace("{}: Init device state transaction {} putting if absent operational data started.", id, writeTx.getIdentifier()); + writeTx.put(LogicalDatastoreType.OPERATIONAL, path, node); + logger.trace("{}: Init device state transaction {} putting operational data ended.", id, writeTx.getIdentifier()); + + logger.trace("{}: Init device state transaction {} putting if absent config data started.", id, writeTx.getIdentifier()); + writeTx.put(LogicalDatastoreType.CONFIGURATION, path, getNodeWithId(id)); + logger.trace("{}: Init device state transaction {} putting config data ended.", id, writeTx.getIdentifier()); + + commitTransaction(writeTx, "init"); + } + + public void updateDeviceData(boolean up, NetconfDeviceCapabilities capabilities) { + final Node data = buildDataForNetconfNode(up, capabilities); + + final WriteTransaction writeTx = dataService.newWriteOnlyTransaction(); + logger.trace("{}: Update device state transaction {} merging operational data started.", id, writeTx.getIdentifier()); + writeTx.put(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath(), data); + logger.trace("{}: Update device state transaction {} merging operational data ended.", id, writeTx.getIdentifier()); + + commitTransaction(writeTx, "update"); + } + + public void setDeviceAsFailed(Throwable throwable) { + String reason = (throwable != null && throwable.getMessage() != null) ? throwable.getMessage() : UNKNOWN_REASON; + + final NetconfNode netconfNode = new NetconfNodeBuilder().setConnectionStatus(ConnectionStatus.UnableToConnect).setConnectedMessage(reason).build(); + final Node data = getNodeIdBuilder(id).addAugmentation(NetconfNode.class, netconfNode).build(); + + final WriteTransaction writeTx = dataService.newWriteOnlyTransaction(); + logger.trace("{}: Setting device state as failed {} putting operational data started.", id, writeTx.getIdentifier()); + writeTx.put(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath(), data); + logger.trace("{}: Setting device state as failed {} putting operational data ended.", id, writeTx.getIdentifier()); + + commitTransaction(writeTx, "update-failed-device"); + } + + private Node buildDataForNetconfNode(boolean up, NetconfDeviceCapabilities capabilities) { + List capabilityList = new ArrayList<>(); + capabilityList.addAll(capabilities.getNonModuleBasedCapabilities()); + capabilityList.addAll(FluentIterable.from(capabilities.getResolvedCapabilities()).transform(AVAILABLE_CAPABILITY_TRANSFORMER).toList()); + final AvailableCapabilitiesBuilder avCapabalitiesBuilder = new AvailableCapabilitiesBuilder(); + avCapabalitiesBuilder.setAvailableCapability(capabilityList); + + final UnavailableCapabilities unavailableCapabilities = + new UnavailableCapabilitiesBuilder().setUnavailableCapability(FluentIterable.from(capabilities.getUnresolvedCapabilites().entrySet()) + .transform(UNAVAILABLE_CAPABILITY_TRANSFORMER).toList()).build(); + + final NetconfNodeBuilder netconfNodeBuilder = new NetconfNodeBuilder() + .setHost(id.getHost()) + .setPort(new PortNumber(id.getAddress().getPort())) + .setConnectionStatus(up ? ConnectionStatus.Connected : ConnectionStatus.Connecting) + .setAvailableCapabilities(avCapabalitiesBuilder.build()) + .setUnavailableCapabilities(unavailableCapabilities); + + final NodeBuilder nodeBuilder = getNodeIdBuilder(id); + final Node node = nodeBuilder.addAugmentation(NetconfNode.class, netconfNodeBuilder.build()).build(); + + return node; + } + + public void removeDeviceConfiguration() { + final WriteTransaction writeTx = dataService.newWriteOnlyTransaction(); + + logger.trace("{}: Close device state transaction {} removing all data started.", id, writeTx.getIdentifier()); + writeTx.delete(LogicalDatastoreType.CONFIGURATION, id.getTopologyBindingPath()); + writeTx.delete(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath()); + logger.trace("{}: Close device state transaction {} removing all data ended.", id, writeTx.getIdentifier()); + + commitTransaction(writeTx, "close"); + } + + private void createNetworkTopologyIfNotPresent(final WriteTransaction writeTx) { + + final NetworkTopology networkTopology = new NetworkTopologyBuilder().build(); + logger.trace("{}: Merging {} container to ensure its presence", id, networkTopology.QNAME, writeTx.getIdentifier()); + writeTx.merge(LogicalDatastoreType.CONFIGURATION, networkTopologyPath, networkTopology); + writeTx.merge(LogicalDatastoreType.OPERATIONAL, networkTopologyPath, networkTopology); + + final Topology topology = new TopologyBuilder().setTopologyId(new TopologyId(TopologyNetconf.QNAME.getLocalName())).build(); + logger.trace("{}: Merging {} container to ensure its presence", id, topology.QNAME, writeTx.getIdentifier()); + writeTx.merge(LogicalDatastoreType.CONFIGURATION, topologyListPath, topology); + writeTx.merge(LogicalDatastoreType.OPERATIONAL, topologyListPath, topology); + } + + private void commitTransaction(final WriteTransaction transaction, final String txType) { + logger.trace("{}: Committing Transaction {}:{}", id, txType, transaction.getIdentifier()); + final CheckedFuture result = transaction.submit(); + + Futures.addCallback(result, new FutureCallback() { + @Override + public void onSuccess(final Void result) { + logger.trace("{}: Transaction({}) {} SUCCESSFUL", id, txType, transaction.getIdentifier()); + } + + @Override + public void onFailure(final Throwable t) { + logger.error("{}: Transaction({}) {} FAILED!", id, txType, transaction.getIdentifier(), t); + throw new IllegalStateException(id + " Transaction(" + txType + ") not committed correctly", t); + } + }); + + } + + private static Node getNodeWithId(final RemoteDeviceId id) { + final NodeBuilder builder = getNodeIdBuilder(id); + return builder.build(); + } + + private static NodeBuilder getNodeIdBuilder(final RemoteDeviceId id) { + final NodeBuilder nodeBuilder = new NodeBuilder(); + nodeBuilder.setKey(new NodeKey(new NodeId(id.getName()))); + return nodeBuilder; + } + + @Override + public void close() throws Exception { + removeDeviceConfiguration(); + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java index 165d9c452d..435ef9915d 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java @@ -14,7 +14,7 @@ import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.yangtools.yang.common.RpcResult; @@ -27,11 +27,11 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction { protected final RemoteDeviceId id; protected final NetconfBaseOps netOps; protected final DataNormalizer normalizer; - protected final NetconfSessionCapabilities netconfSessionPreferences; + protected final NetconfSessionPreferences netconfSessionPreferences; // Allow commit to be called only once protected boolean finished = false; - public AbstractWriteTx(final NetconfBaseOps netOps, final RemoteDeviceId id, final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) { + public AbstractWriteTx(final NetconfBaseOps netOps, final RemoteDeviceId id, final DataNormalizer normalizer, final NetconfSessionPreferences netconfSessionPreferences) { this.netOps = netOps; this.id = id; this.normalizer = normalizer; diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTx.java index a3186f8e69..00bdbb6db0 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTx.java @@ -110,7 +110,7 @@ public final class ReadOnlyTx implements DOMDataReadOnlyTransaction { private CheckedFuture>, ReadFailedException> readOperationalData( final YangInstanceIdentifier path) { - final ListenableFuture> configCandidate = netconfOps.getConfigRunning(loggingCallback, Optional.fromNullable(path)); + final ListenableFuture> configCandidate = netconfOps.get(loggingCallback, Optional.fromNullable(path)); // Find data node and normalize its content final ListenableFuture>> transformedFuture = Futures.transform(configCandidate, new Function, Optional>>() { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java index 4a9a9398d0..710700b362 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java @@ -12,7 +12,7 @@ import com.google.common.base.Function; import com.google.common.util.concurrent.ListenableFuture; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; import org.opendaylight.controller.sal.connect.netconf.util.NetconfRpcFutureCallback; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; @@ -32,7 +32,7 @@ public class WriteCandidateRunningTx extends WriteCandidateTx { private static final Logger LOG = LoggerFactory.getLogger(WriteCandidateRunningTx.class); - public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) { + public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final DataNormalizer normalizer, final NetconfSessionPreferences netconfSessionPreferences) { super(id, netOps, normalizer, netconfSessionPreferences); } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java index 0ea6298398..f9bf3c75fd 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java @@ -17,7 +17,7 @@ import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; import org.opendaylight.controller.sal.connect.netconf.util.NetconfRpcFutureCallback; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; @@ -69,7 +69,7 @@ public class WriteCandidateTx extends AbstractWriteTx { } }; - public WriteCandidateTx(final RemoteDeviceId id, final NetconfBaseOps rpc, final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) { + public WriteCandidateTx(final RemoteDeviceId id, final NetconfBaseOps rpc, final DataNormalizer normalizer, final NetconfSessionPreferences netconfSessionPreferences) { super(rpc, id, normalizer, netconfSessionPreferences); } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java index 28173b1da3..f92e40fb57 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java @@ -17,7 +17,7 @@ import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; import org.opendaylight.controller.sal.connect.netconf.util.NetconfRpcFutureCallback; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; @@ -50,7 +50,7 @@ public class WriteRunningTx extends AbstractWriteTx { private static final Logger LOG = LoggerFactory.getLogger(WriteRunningTx.class); public WriteRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, - final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) { + final DataNormalizer normalizer, final NetconfSessionPreferences netconfSessionPreferences) { super(netOps, id, normalizer, netconfSessionPreferences); } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfRemoteSchemaYangSourceProvider.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfRemoteSchemaYangSourceProvider.java index 5a528bc418..3565ba1113 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfRemoteSchemaYangSourceProvider.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/schema/NetconfRemoteSchemaYangSourceProvider.java @@ -8,7 +8,7 @@ package org.opendaylight.controller.sal.connect.netconf.schema; import com.google.common.base.Function; -import com.google.common.base.Objects; +import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.CheckedFuture; @@ -166,7 +166,7 @@ public final class NetconfRemoteSchemaYangSourceProvider implements SchemaSource } @Override - protected Objects.ToStringHelper addToStringAttributes(final Objects.ToStringHelper toStringHelper) { + protected MoreObjects.ToStringHelper addToStringAttributes(final MoreObjects.ToStringHelper toStringHelper) { return toStringHelper.add("device", id); } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java index 8ac8a48b45..40e890e80c 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java @@ -174,33 +174,17 @@ public final class NetconfBaseOps { return getConfig(callback, NETCONF_CANDIDATE_QNAME, filterPath); } - public ListenableFuture> get(final FutureCallback> callback, final QName datastore, final Optional filterPath) { + public ListenableFuture> get(final FutureCallback> callback, final Optional filterPath) { Preconditions.checkNotNull(callback); - Preconditions.checkNotNull(datastore); final ListenableFuture> future; - if (filterPath.isPresent()) { - final Node node = toFilterStructure(filterPath.get()); - future = rpc.invokeRpc(NETCONF_GET_QNAME, - NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, getSourceNode(datastore), node)); - } else { - future = rpc.invokeRpc(NETCONF_GET_QNAME, - NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, getSourceNode(datastore))); - } + final Node node = filterPath.isPresent() ? toFilterStructure(filterPath.get()) : NetconfMessageTransformUtil.GET_RPC_CONTENT; + future = rpc.invokeRpc(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, node)); Futures.addCallback(future, callback); return future; } - public ListenableFuture> getRunning(final FutureCallback> callback, final Optional filterPath) { - return get(callback, NETCONF_RUNNING_QNAME, filterPath); - } - - public ListenableFuture> getCandidate(final FutureCallback> callback, final Optional filterPath) { - return get(callback, NETCONF_CANDIDATE_QNAME, filterPath); - } - - public ListenableFuture> editConfigCandidate(final FutureCallback> callback, final CompositeNode editStructure, final ModifyAction modifyAction, final boolean rollback) { return editConfig(callback, NETCONF_CANDIDATE_QNAME, editStructure, Optional.of(modifyAction), rollback); } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java index 9eba24179f..371907e0fe 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java @@ -26,7 +26,9 @@ import javax.annotation.Nullable; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscriptionInput; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.RpcError; import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity; @@ -51,6 +53,7 @@ import org.w3c.dom.Element; public class NetconfMessageTransformUtil { public static final String MESSAGE_ID_ATTR = "message-id"; + public static final QName CREATE_SUBSCRIPTION_RPC_QNAME = QName.cachedReference(QName.create(CreateSubscriptionInput.QNAME, "create-subscription")); private NetconfMessageTransformUtil() {} @@ -61,6 +64,8 @@ public class NetconfMessageTransformUtil { public static final QName IETF_NETCONF_MONITORING_SCHEMA_VERSION = QName.create(IETF_NETCONF_MONITORING, "version"); public static final QName IETF_NETCONF_MONITORING_SCHEMA_NAMESPACE = QName.create(IETF_NETCONF_MONITORING, "namespace"); + public static final QName IETF_NETCONF_NOTIFICATIONS = QName.create(NetconfCapabilityChange.QNAME, "ietf-netconf-notifications"); + public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"); public static QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf"); public static QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data"); @@ -91,6 +96,9 @@ public class NetconfMessageTransformUtil { public static URI NETCONF_CANDIDATE_URI = URI .create("urn:ietf:params:netconf:capability:candidate:1.0"); + public static URI NETCONF_NOTIFICATONS_URI = URI + .create("urn:ietf:params:netconf:capability:notification:1.0"); + public static URI NETCONF_RUNNING_WRITABLE_URI = URI .create("urn:ietf:params:netconf:capability:writable-running:1.0"); @@ -105,6 +113,14 @@ public class NetconfMessageTransformUtil { public static final CompositeNode COMMIT_RPC_CONTENT = NodeFactory.createImmutableCompositeNode(NETCONF_COMMIT_QNAME, null, Collections.>emptyList()); + // Get message + public static final CompositeNode GET_RPC_CONTENT = + NodeFactory.createImmutableCompositeNode(NETCONF_GET_QNAME, null, Collections.>emptyList()); + + // Create-subscription changes message + public static final CompositeNode CREATE_SUBSCRIPTION_RPC_CONTENT = + NodeFactory.createImmutableCompositeNode(CREATE_SUBSCRIPTION_RPC_QNAME, null, Collections.>emptyList()); + public static Node toFilterStructure(final YangInstanceIdentifier identifier) { Node previous = null; if (Iterables.isEmpty(identifier.getPathArguments())) { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/RemoteDeviceId.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/RemoteDeviceId.java index 333b42e1c5..7f13a7a5dd 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/RemoteDeviceId.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/util/RemoteDeviceId.java @@ -7,33 +7,67 @@ */ package org.opendaylight.controller.sal.connect.util; +import com.google.common.base.Preconditions; +import java.net.InetSocketAddress; import org.opendaylight.controller.config.api.ModuleIdentifier; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Host; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.HostBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.network.topology.topology.topology.types.TopologyNetconf; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; - -import com.google.common.base.Preconditions; +import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -public class RemoteDeviceId { +public final class RemoteDeviceId { private final String name; private final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier path; private final InstanceIdentifier bindingPath; private final NodeKey key; + private final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier topologyPath; + private final InstanceIdentifier topologyBindingPath; + private InetSocketAddress address; + private Host host; + @Deprecated public RemoteDeviceId(final ModuleIdentifier identifier) { this(Preconditions.checkNotNull(identifier).getInstanceName()); } + public RemoteDeviceId(final ModuleIdentifier identifier, Host host) { + this(identifier); + this.host = host; + } + + public RemoteDeviceId(final ModuleIdentifier identifier, InetSocketAddress address) { + this(identifier); + this.address = address; + this.host = buildHost(); + } + + @Deprecated public RemoteDeviceId(final String name) { Preconditions.checkNotNull(name); this.name = name; this.key = new NodeKey(new NodeId(name)); this.path = createBIPath(name); this.bindingPath = createBindingPath(key); + this.topologyPath = createBIPathForTopology(name); + this.topologyBindingPath = createBindingPathForTopology(key); + } + + public RemoteDeviceId(final String name, InetSocketAddress address) { + this(name); + this.address = address; + this.host = buildHost(); } private static InstanceIdentifier createBindingPath(final NodeKey key) { @@ -48,6 +82,32 @@ public class RemoteDeviceId { return builder.build(); } + private static InstanceIdentifier createBindingPathForTopology(final NodeKey key) { + final InstanceIdentifier networkTopology = InstanceIdentifier.builder(NetworkTopology.class).build(); + final KeyedInstanceIdentifier topology = networkTopology.child(Topology.class, new TopologyKey(new TopologyId(TopologyNetconf.QNAME.getLocalName()))); + return topology + .child(org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node.class, + new org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey + (new org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId(key.getId().getValue()))); + } + + private static org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier createBIPathForTopology(final String name) { + final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder builder = + org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.builder(); + builder + .node(NetworkTopology.QNAME) + .nodeWithKey(Topology.QNAME, QName.create(Topology.QNAME, "topology-id"), TopologyNetconf.QNAME.getLocalName()) + .nodeWithKey(org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node.QNAME, + QName.create(org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node.QNAME, "node-id"), name); + return builder.build(); + } + + private Host buildHost() { + return address.getAddress().getHostAddress() != null + ? HostBuilder.getDefaultInstance(address.getAddress().getHostAddress()) + : HostBuilder.getDefaultInstance(address.getAddress().getHostName()); + } + public String getName() { return name; } @@ -64,6 +124,22 @@ public class RemoteDeviceId { return key; } + public InstanceIdentifier getTopologyBindingPath() { + return topologyBindingPath; + } + + public YangInstanceIdentifier getTopologyPath() { + return topologyPath; + } + + public InetSocketAddress getAddress() { + return address; + } + + public Host getHost() { + return host; + } + @Override public String toString() { return "RemoteDevice{" + name +'}'; diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/netconf-node-topology.yang b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/netconf-node-topology.yang new file mode 100644 index 0000000000..11bf6a549c --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/netconf-node-topology.yang @@ -0,0 +1,75 @@ +module netconf-node-topology { + namespace "urn:opendaylight:netconf-node-topology"; + prefix "nettop"; + + import network-topology { prefix nt; revision-date 2013-10-21; } + import yang-ext { prefix ext; revision-date "2013-07-09";} + import ietf-inet-types { prefix inet; revision-date "2010-09-24"; } + + revision "2015-01-14" { + description "Initial revision of Topology model"; + } + + augment "/nt:network-topology/nt:topology/nt:topology-types" { + container topology-netconf { + } + } + + grouping netconf-node-fields { + leaf connection-status { + type enumeration { + enum connecting; + enum connected; + enum unable-to-connect; + } + } + + leaf host { + type inet:host; + } + + leaf port { + type inet:port-number; + } + + leaf connected-message { + type string; + } + + container available-capabilities { + leaf-list available-capability { + type string; + } + } + + container unavailable-capabilities { + list unavailable-capability { + leaf capability { + type string; + } + + leaf failure-reason { + type enumeration { + enum missing-source; + enum unable-to-resolve; + } + } + } + } + + container pass-through { + when "../connection-status = connected"; + description + "When the underlying node is connected, its NETCONF context + is available verbatim under this container through the + mount extension."; + } + } + + augment "/nt:network-topology/nt:topology/nt:node" { + when "../../nt:topology-types/topology-netconf"; + ext:augment-identifier "netconf-node"; + + uses netconf-node-fields; + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang index e13398b1df..7059a14aa3 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang @@ -66,6 +66,13 @@ module odl-sal-netconf-connector-cfg { } } + leaf reconnect-on-changed-schema { + type boolean; + default false; + description "If true, the connector would auto disconnect/reconnect when schemas are changed in the remote device. + The connector subscribes (right after connect) to base netconf notifications and listens for netconf-capability-change notification"; + } + container dom-registry { uses config:service-ref { refine type { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java index 80ac4d7376..ec945e050b 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java @@ -17,6 +17,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; + import com.google.common.base.Optional; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; @@ -36,10 +37,10 @@ import org.mockito.stubbing.Answer; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; import org.opendaylight.controller.sal.connect.api.MessageTransformer; -import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.controller.sal.connect.api.SchemaSourceProviderFactory; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceRpc; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; @@ -90,7 +91,7 @@ public class NetconfDeviceTest { private static final NetconfStateSchemas.NetconfStateSchemasResolver stateSchemasResolver = new NetconfStateSchemas.NetconfStateSchemasResolver() { @Override - public NetconfStateSchemas resolve(final NetconfDeviceRpc deviceRpc, final NetconfSessionCapabilities remoteSessionCapabilities, final RemoteDeviceId id) { + public NetconfStateSchemas resolve(final NetconfDeviceRpc deviceRpc, final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id) { return NetconfStateSchemas.EMPTY; } }; @@ -99,8 +100,8 @@ public class NetconfDeviceTest { public void testNetconfDeviceFailFirstSchemaFailSecondEmpty() throws Exception { final ArrayList capList = Lists.newArrayList(TEST_CAPABILITY); - final RemoteDeviceHandler facade = getFacade(); - final RemoteDeviceCommunicator listener = getListener(); + final RemoteDeviceHandler facade = getFacade(); + final NetconfDeviceCommunicator listener = getListener(); final SchemaContextFactory schemaFactory = getSchemaFactory(); @@ -114,9 +115,9 @@ public class NetconfDeviceTest { final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), schemaFactory, stateSchemasResolver); - final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer()); + final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer(), true); // Monitoring not supported - final NetconfSessionCapabilities sessionCaps = getSessionCaps(false, capList); + final NetconfSessionPreferences sessionCaps = getSessionCaps(false, capList); device.onRemoteSessionUp(sessionCaps, listener); Mockito.verify(facade, Mockito.timeout(5000)).onDeviceDisconnected(); @@ -126,8 +127,8 @@ public class NetconfDeviceTest { @Test public void testNetconfDeviceMissingSource() throws Exception { - final RemoteDeviceHandler facade = getFacade(); - final RemoteDeviceCommunicator listener = getListener(); + final RemoteDeviceHandler facade = getFacade(); + final NetconfDeviceCommunicator listener = getListener(); final SchemaContextFactory schemaFactory = getSchemaFactory(); @@ -146,12 +147,12 @@ public class NetconfDeviceTest { final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), schemaFactory, stateSchemasResolver); - final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer()); + final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer(), true); // Monitoring supported - final NetconfSessionCapabilities sessionCaps = getSessionCaps(true, Lists.newArrayList(TEST_CAPABILITY, TEST_CAPABILITY2)); + final NetconfSessionPreferences sessionCaps = getSessionCaps(true, Lists.newArrayList(TEST_CAPABILITY, TEST_CAPABILITY2)); device.onRemoteSessionUp(sessionCaps, listener); - Mockito.verify(facade, Mockito.timeout(5000)).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionCapabilities.class), any(RpcImplementation.class)); + Mockito.verify(facade, Mockito.timeout(5000)).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionPreferences.class), any(RpcImplementation.class)); Mockito.verify(schemaFactory, times(2)).createSchemaContext(anyCollectionOf(SourceIdentifier.class)); } @@ -165,21 +166,21 @@ public class NetconfDeviceTest { @Test public void testNotificationBeforeSchema() throws Exception { - final RemoteDeviceHandler facade = getFacade(); - final RemoteDeviceCommunicator listener = getListener(); + final RemoteDeviceHandler facade = getFacade(); + final NetconfDeviceCommunicator listener = getListener(); final MessageTransformer messageTransformer = getMessageTransformer(); final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), getSchemaFactory(), stateSchemasResolver); - final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), messageTransformer); + final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), messageTransformer, true); device.onNotification(netconfMessage); device.onNotification(netconfMessage); verify(facade, times(0)).onNotification(any(CompositeNode.class)); - final NetconfSessionCapabilities sessionCaps = getSessionCaps(true, + final NetconfSessionPreferences sessionCaps = getSessionCaps(true, Lists.newArrayList(TEST_CAPABILITY)); device.onRemoteSessionUp(sessionCaps, listener); @@ -194,22 +195,22 @@ public class NetconfDeviceTest { @Test public void testNetconfDeviceReconnect() throws Exception { - final RemoteDeviceHandler facade = getFacade(); - final RemoteDeviceCommunicator listener = getListener(); + final RemoteDeviceHandler facade = getFacade(); + final NetconfDeviceCommunicator listener = getListener(); final SchemaContextFactory schemaContextProviderFactory = getSchemaFactory(); final MessageTransformer messageTransformer = getMessageTransformer(); final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), schemaContextProviderFactory, stateSchemasResolver); - final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), messageTransformer); - final NetconfSessionCapabilities sessionCaps = getSessionCaps(true, + final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), messageTransformer, true); + final NetconfSessionPreferences sessionCaps = getSessionCaps(true, Lists.newArrayList(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION)); device.onRemoteSessionUp(sessionCaps, listener); verify(schemaContextProviderFactory, timeout(5000)).createSchemaContext(any(Collection.class)); verify(messageTransformer, timeout(5000)).onGlobalContextUpdated(any(SchemaContext.class)); - verify(facade, timeout(5000)).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionCapabilities.class), any(RpcImplementation.class)); + verify(facade, timeout(5000)).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionPreferences.class), any(RpcImplementation.class)); device.onRemoteSessionDown(); verify(facade, timeout(5000)).onDeviceDisconnected(); @@ -218,7 +219,7 @@ public class NetconfDeviceTest { verify(schemaContextProviderFactory, timeout(5000).times(2)).createSchemaContext(any(Collection.class)); verify(messageTransformer, timeout(5000).times(3)).onGlobalContextUpdated(any(SchemaContext.class)); - verify(facade, timeout(5000).times(2)).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionCapabilities.class), any(RpcImplementation.class)); + verify(facade, timeout(5000).times(2)).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionPreferences.class), any(RpcImplementation.class)); } private SchemaContextFactory getSchemaFactory() { @@ -236,9 +237,9 @@ public class NetconfDeviceTest { return parser.resolveSchemaContext(models); } - private RemoteDeviceHandler getFacade() throws Exception { - final RemoteDeviceHandler remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class); - doNothing().when(remoteDeviceHandler).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionCapabilities.class), any(RpcImplementation.class)); + private RemoteDeviceHandler getFacade() throws Exception { + final RemoteDeviceHandler remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class); + doNothing().when(remoteDeviceHandler).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionPreferences.class), any(RpcImplementation.class)); doNothing().when(remoteDeviceHandler).onDeviceDisconnected(); doNothing().when(remoteDeviceHandler).onNotification(any(CompositeNode.class)); return remoteDeviceHandler; @@ -283,7 +284,7 @@ public class NetconfDeviceTest { return messageTransformer; } - public NetconfSessionCapabilities getSessionCaps(final boolean addMonitor, final Collection additionalCapabilities) { + public NetconfSessionPreferences getSessionCaps(final boolean addMonitor, final Collection additionalCapabilities) { final ArrayList capabilities = Lists.newArrayList( XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0, XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1); @@ -294,12 +295,12 @@ public class NetconfDeviceTest { capabilities.addAll(additionalCapabilities); - return NetconfSessionCapabilities.fromStrings( + return NetconfSessionPreferences.fromStrings( capabilities); } - public RemoteDeviceCommunicator getListener() throws Exception { - final RemoteDeviceCommunicator remoteDeviceCommunicator = mockCloseableClass(RemoteDeviceCommunicator.class); + public NetconfDeviceCommunicator getListener() throws Exception { + final NetconfDeviceCommunicator remoteDeviceCommunicator = mockCloseableClass(NetconfDeviceCommunicator.class); doReturn(Futures.immediateFuture(rpcResult)).when(remoteDeviceCommunicator).sendRequest(any(NetconfMessage.class), any(QName.class)); return remoteDeviceCommunicator; } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java index a24034d2f0..68fe87fb60 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java @@ -56,10 +56,10 @@ import org.opendaylight.controller.netconf.api.NetconfTerminationReason; import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; import org.opendaylight.controller.netconf.client.NetconfClientSession; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; +import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfiguration; import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword; import org.opendaylight.controller.sal.connect.api.RemoteDevice; -import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.protocol.framework.ReconnectStrategy; @@ -77,7 +77,7 @@ public class NetconfDeviceCommunicatorTest { NetconfClientSession mockSession; @Mock - RemoteDevice mockDevice; + RemoteDevice mockDevice; NetconfDeviceCommunicator communicator; @@ -85,16 +85,15 @@ public class NetconfDeviceCommunicatorTest { public void setUp() throws Exception { MockitoAnnotations.initMocks( this ); - communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test" ), mockDevice ); + communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test" ), mockDevice); } @SuppressWarnings("unchecked") - void setupSession() - { - doReturn( Collections.emptySet() ).when( mockSession ).getServerCapabilities(); - doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionCapabilities.class ), - any( RemoteDeviceCommunicator.class ) ); - communicator.onSessionUp( mockSession ); + void setupSession() { + doReturn(Collections.emptySet()).when(mockSession).getServerCapabilities(); + doNothing().when(mockDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class), + any(NetconfDeviceCommunicator.class)); + communicator.onSessionUp(mockSession); } private ListenableFuture> sendRequest() throws Exception { @@ -130,16 +129,16 @@ public class NetconfDeviceCommunicatorTest { testCapability ); doReturn( serverCapabilities ).when( mockSession ).getServerCapabilities(); - ArgumentCaptor netconfSessionCapabilities = - ArgumentCaptor.forClass( NetconfSessionCapabilities.class ); - doNothing().when( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) ); + ArgumentCaptor NetconfSessionPreferences = + ArgumentCaptor.forClass( NetconfSessionPreferences.class ); + doNothing().when( mockDevice ).onRemoteSessionUp( NetconfSessionPreferences.capture(), eq( communicator ) ); communicator.onSessionUp( mockSession ); verify( mockSession ).getServerCapabilities(); - verify( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) ); + verify( mockDevice ).onRemoteSessionUp( NetconfSessionPreferences.capture(), eq( communicator ) ); - NetconfSessionCapabilities actualCapabilites = netconfSessionCapabilities.getValue(); + NetconfSessionPreferences actualCapabilites = NetconfSessionPreferences.getValue(); assertEquals( "containsModuleCapability", true, actualCapabilites.containsNonModuleCapability( NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()) ); assertEquals( "containsModuleCapability", false, actualCapabilites.containsNonModuleCapability(testCapability) ); @@ -340,7 +339,7 @@ public class NetconfDeviceCommunicatorTest { */ @Test public void testNetconfDeviceReconnectInCommunicator() throws Exception { - final RemoteDevice device = mock(RemoteDevice.class); + final RemoteDevice device = mock(RemoteDevice.class); final TimedReconnectStrategy timedReconnectStrategy = new TimedReconnectStrategy(GlobalEventExecutor.INSTANCE, 10000, 0, 1.0, null, 100L, null); final ReconnectStrategy reconnectStrategy = spy(new ReconnectStrategy() { @@ -360,11 +359,11 @@ public class NetconfDeviceCommunicatorTest { } }); - final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(new RemoteDeviceId("test"), device); final EventLoopGroup group = new NioEventLoopGroup(); final Timer time = new HashedWheelTimer(); try { - final NetconfClientConfiguration cfg = NetconfReconnectingClientConfigurationBuilder.create() + final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(new RemoteDeviceId("test"), device); + final NetconfReconnectingClientConfiguration cfg = NetconfReconnectingClientConfigurationBuilder.create() .withAddress(new InetSocketAddress("localhost", 65000)) .withReconnectStrategy(reconnectStrategy) .withConnectStrategyFactory(new ReconnectStrategyFactory() { @@ -379,7 +378,6 @@ public class NetconfDeviceCommunicatorTest { .withSessionListener(listener) .build(); - listener.initializeRemoteConnection(new NetconfClientDispatcherImpl(group, group, time), cfg); verify(reconnectStrategy, timeout((int) TimeUnit.MINUTES.toMillis(3)).times(101)).scheduleReconnect(any(Throwable.class)); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferencesTest.java similarity index 81% rename from opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java rename to opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferencesTest.java index ae7d9c28ac..653b641353 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferencesTest.java @@ -10,7 +10,7 @@ import org.junit.Test; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.yangtools.yang.common.QName; -public class NetconfSessionCapabilitiesTest { +public class NetconfSessionPreferencesTest { @Test public void testMerge() throws Exception { @@ -21,7 +21,7 @@ public class NetconfSessionCapabilitiesTest { "urn:ietf:params:netconf:base:1.0", "urn:ietf:params:netconf:capability:rollback-on-error:1.0" ); - final NetconfSessionCapabilities sessionCaps1 = NetconfSessionCapabilities.fromStrings(caps1); + final NetconfSessionPreferences sessionCaps1 = NetconfSessionPreferences.fromStrings(caps1); assertCaps(sessionCaps1, 2, 3); final List caps2 = Lists.newArrayList( @@ -29,10 +29,10 @@ public class NetconfSessionCapabilitiesTest { "namespace:4?module=module4&revision=2012-12-12", "randomNonModuleCap" ); - final NetconfSessionCapabilities sessionCaps2 = NetconfSessionCapabilities.fromStrings(caps2); + final NetconfSessionPreferences sessionCaps2 = NetconfSessionPreferences.fromStrings(caps2); assertCaps(sessionCaps2, 1, 2); - final NetconfSessionCapabilities merged = sessionCaps1.replaceModuleCaps(sessionCaps2); + final NetconfSessionPreferences merged = sessionCaps1.replaceModuleCaps(sessionCaps2); assertCaps(merged, 2, 2 + 1 /*Preserved monitoring*/); for (final QName qName : sessionCaps2.getModuleBasedCaps()) { assertThat(merged.getModuleBasedCaps(), hasItem(qName)); @@ -52,11 +52,11 @@ public class NetconfSessionCapabilitiesTest { "namespace:2?module=module2&RANDOMSTRING;revision=2013-12-12" // This one should be ignored(same as first), since revision is in wrong format ); - final NetconfSessionCapabilities sessionCaps1 = NetconfSessionCapabilities.fromStrings(caps1); + final NetconfSessionPreferences sessionCaps1 = NetconfSessionPreferences.fromStrings(caps1); assertCaps(sessionCaps1, 0, 3); } - private void assertCaps(final NetconfSessionCapabilities sessionCaps1, final int nonModuleCaps, final int moduleCaps) { + private void assertCaps(final NetconfSessionPreferences sessionCaps1, final int nonModuleCaps, final int moduleCaps) { assertEquals(nonModuleCaps, sessionCaps1.getNonModuleCaps().size()); assertEquals(moduleCaps, sessionCaps1.getModuleBasedCaps().size()); } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceTopologyAdapterTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceTopologyAdapterTest.java new file mode 100644 index 0000000000..a1551b23b6 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceTopologyAdapterTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.sal.connect.netconf.sal; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.google.common.util.concurrent.Futures; +import java.net.InetSocketAddress; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCapabilities; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class NetconfDeviceTopologyAdapterTest { + + private RemoteDeviceId id = new RemoteDeviceId("test", new InetSocketAddress("localhost", 22)); + + @Mock + private DataBroker broker; + @Mock + private WriteTransaction writeTx; + @Mock + private Node data; + + private String txIdent = "test transaction"; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doReturn(writeTx).when(broker).newWriteOnlyTransaction(); + doNothing().when(writeTx).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class)); + doNothing().when(writeTx).merge(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class)); + + doReturn(txIdent).when(writeTx).getIdentifier(); + } + + @Test + public void testFailedDevice() throws Exception { + doReturn(Futures.immediateCheckedFuture(null)).when(writeTx).submit(); + + NetconfDeviceTopologyAdapter adapter = new NetconfDeviceTopologyAdapter(id, broker); + adapter.setDeviceAsFailed(null); + + verify(broker, times(2)).newWriteOnlyTransaction(); + verify(writeTx, times(3)).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class)); + } + + @Test + public void testDeviceUpdate() throws Exception { + doReturn(Futures.immediateCheckedFuture(null)).when(writeTx).submit(); + + NetconfDeviceTopologyAdapter adapter = new NetconfDeviceTopologyAdapter(id, broker); + adapter.updateDeviceData(true, new NetconfDeviceCapabilities()); + + verify(broker, times(2)).newWriteOnlyTransaction(); + verify(writeTx, times(3)).put(any(LogicalDatastoreType.class), any(InstanceIdentifier.class), any(Node.class)); + } + +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java index ce97541fe4..a37fade915 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java @@ -21,7 +21,7 @@ import org.mockito.MockitoAnnotations; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; @@ -60,7 +60,7 @@ public class NetconfDeviceWriteOnlyTxTest { @Test public void testDiscardChanges() { final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc), normalizer, - NetconfSessionCapabilities.fromStrings(Collections.emptySet())); + NetconfSessionPreferences.fromStrings(Collections.emptySet())); final CheckedFuture submitFuture = tx.submit(); try { submitFuture.checkedGet(); @@ -84,7 +84,7 @@ public class NetconfDeviceWriteOnlyTxTest { .when(rpc).invokeRpc(any(QName.class), any(CompositeNode.class)); final WriteRunningTx tx = new WriteRunningTx(id, new NetconfBaseOps(rpc), normalizer, - NetconfSessionCapabilities.fromStrings(Collections.emptySet())); + NetconfSessionPreferences.fromStrings(Collections.emptySet())); try { tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId); } catch (final Exception e) { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTxTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTxTest.java new file mode 100644 index 0000000000..072cb185d0 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTxTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.sal.connect.netconf.sal.tx; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import java.net.InetSocketAddress; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException; +import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +public class ReadOnlyTxTest { + + private static final YangInstanceIdentifier path = YangInstanceIdentifier.create(); + + @Mock + private RpcImplementation rpc; + @Mock + private DataNormalizer normalizer; + @Mock + private CompositeNode mockedNode; + + @Before + public void setUp() throws DataNormalizationException { + MockitoAnnotations.initMocks(this); + doReturn(path).when(normalizer).toLegacy(any(YangInstanceIdentifier.class)); + doReturn(com.google.common.util.concurrent.Futures.immediateFuture(RpcResultBuilder.success(mockedNode).build())).when(rpc).invokeRpc(any(org.opendaylight.yangtools.yang.common.QName.class), any(CompositeNode.class)); + doReturn("node").when(mockedNode).toString(); + } + + @Test + public void testRead() throws Exception { + final NetconfBaseOps netconfOps = new NetconfBaseOps(rpc); + + final ReadOnlyTx readOnlyTx = new ReadOnlyTx(netconfOps, normalizer, new RemoteDeviceId("a", new InetSocketAddress("localhost", 196))); + + readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.create()); + verify(rpc).invokeRpc(Mockito.same(NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME), any(CompositeNode.class)); + readOnlyTx.read(LogicalDatastoreType.OPERATIONAL, path); + verify(rpc).invokeRpc(Mockito.same(NetconfMessageTransformUtil.NETCONF_GET_QNAME), any(CompositeNode.class)); + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/TerminationMonitor.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/TerminationMonitor.java index 48ccd824d4..13399f6f9d 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/TerminationMonitor.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/TerminationMonitor.java @@ -10,16 +10,15 @@ package org.opendaylight.controller.remote.rpc; import akka.actor.Terminated; import akka.actor.UntypedActor; -import akka.event.Logging; -import akka.event.LoggingAdapter; import org.opendaylight.controller.cluster.common.actor.Monitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class TerminationMonitor extends UntypedActor{ - protected final LoggingAdapter LOG = - Logging.getLogger(getContext().system(), this); + private static final Logger LOG = LoggerFactory.getLogger(TerminationMonitor.class); public TerminationMonitor(){ - LOG.info("Created TerminationMonitor"); + LOG.debug("Created TerminationMonitor"); } @Override public void onReceive(Object message) throws Exception { diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/ExecuteRpc.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/ExecuteRpc.java index 4bb5258b40..5d780be641 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/ExecuteRpc.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/ExecuteRpc.java @@ -9,29 +9,28 @@ package org.opendaylight.controller.remote.rpc.messages; import com.google.common.base.Preconditions; -import org.opendaylight.yangtools.yang.common.QName; - import java.io.Serializable; +import org.opendaylight.yangtools.yang.common.QName; public class ExecuteRpc implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1128904894827335676L; - private final String inputCompositeNode; - private final QName rpc; + private final String inputCompositeNode; + private final QName rpc; - public ExecuteRpc(final String inputCompositeNode, final QName rpc) { - Preconditions.checkNotNull(inputCompositeNode, "Composite Node input string should be present"); - Preconditions.checkNotNull(rpc, "rpc Qname should not be null"); + public ExecuteRpc(final String inputCompositeNode, final QName rpc) { + Preconditions.checkNotNull(inputCompositeNode, "Composite Node input string should be present"); + Preconditions.checkNotNull(rpc, "rpc Qname should not be null"); - this.inputCompositeNode = inputCompositeNode; - this.rpc = rpc; - } + this.inputCompositeNode = inputCompositeNode; + this.rpc = rpc; + } - public String getInputCompositeNode() { - return inputCompositeNode; - } + public String getInputCompositeNode() { + return inputCompositeNode; + } - public QName getRpc() { - return rpc; - } + public QName getRpc() { + return rpc; + } } diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/InvokeRpc.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/InvokeRpc.java index 652569b7ba..9c40dbfc58 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/InvokeRpc.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/InvokeRpc.java @@ -8,37 +8,36 @@ package org.opendaylight.controller.remote.rpc.messages; import com.google.common.base.Preconditions; +import java.io.Serializable; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import java.io.Serializable; - public class InvokeRpc implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -2813459607858108953L; - private final QName rpc; - private final YangInstanceIdentifier identifier; - private final CompositeNode input; + private final QName rpc; + private final YangInstanceIdentifier identifier; + private final CompositeNode input; - public InvokeRpc(final QName rpc, final YangInstanceIdentifier identifier, final CompositeNode input) { - Preconditions.checkNotNull(rpc, "rpc qname should not be null"); - Preconditions.checkNotNull(input, "rpc input should not be null"); + public InvokeRpc(final QName rpc, final YangInstanceIdentifier identifier, final CompositeNode input) { + Preconditions.checkNotNull(rpc, "rpc qname should not be null"); + Preconditions.checkNotNull(input, "rpc input should not be null"); - this.rpc = rpc; - this.identifier = identifier; - this.input = input; - } + this.rpc = rpc; + this.identifier = identifier; + this.input = input; + } - public QName getRpc() { - return rpc; - } + public QName getRpc() { + return rpc; + } - public YangInstanceIdentifier getIdentifier() { - return identifier; - } + public YangInstanceIdentifier getIdentifier() { + return identifier; + } - public CompositeNode getInput() { - return input; - } + public CompositeNode getInput() { + return input; + } } diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/RpcResponse.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/RpcResponse.java index 387cb90112..e6b208cb6f 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/RpcResponse.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/messages/RpcResponse.java @@ -10,14 +10,15 @@ package org.opendaylight.controller.remote.rpc.messages; import java.io.Serializable; public class RpcResponse implements Serializable { - private static final long serialVersionUID = 1L; - private final String resultCompositeNode; + private static final long serialVersionUID = -4211279498688989245L; - public RpcResponse(final String resultCompositeNode) { - this.resultCompositeNode = resultCompositeNode; - } + private final String resultCompositeNode; - public String getResultCompositeNode() { - return resultCompositeNode; - } + public RpcResponse(final String resultCompositeNode) { + this.resultCompositeNode = resultCompositeNode; + } + + public String getResultCompositeNode() { + return resultCompositeNode; + } } diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RoutingTable.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RoutingTable.java index 52b1106c87..f67657f692 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RoutingTable.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RoutingTable.java @@ -17,7 +17,7 @@ import org.opendaylight.controller.remote.rpc.registry.gossip.Copier; import org.opendaylight.controller.sal.connector.api.RpcRouter; public class RoutingTable implements Copier, Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 5592610415175278760L; private final Map, Long> table = new HashMap<>(); private ActorRef router; diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RpcRegistry.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RpcRegistry.java index 845c1c819a..219646d847 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RpcRegistry.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RpcRegistry.java @@ -8,8 +8,6 @@ package org.opendaylight.controller.remote.rpc.registry; import akka.actor.ActorRef; -import akka.event.Logging; -import akka.event.LoggingAdapter; import akka.japi.Option; import akka.japi.Pair; import com.google.common.base.Preconditions; @@ -32,8 +30,6 @@ import org.opendaylight.controller.sal.connector.api.RpcRouter.RouteIdentifier; */ public class RpcRegistry extends BucketStore { - final LoggingAdapter log = Logging.getLogger(getContext().system(), this); - public RpcRegistry() { getLocalBucket().setData(new RoutingTable()); } diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/BucketImpl.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/BucketImpl.java index b81175e9a2..4c4573d909 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/BucketImpl.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/BucketImpl.java @@ -10,7 +10,7 @@ package org.opendaylight.controller.remote.rpc.registry.gossip; import java.io.Serializable; public class BucketImpl> implements Bucket, Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 294779770032719196L; private Long version = System.currentTimeMillis(); diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/BucketStore.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/BucketStore.java index 934609b7cf..628deb4311 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/BucketStore.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/BucketStore.java @@ -13,8 +13,6 @@ import akka.actor.ActorRefProvider; import akka.actor.Address; import akka.actor.Props; import akka.cluster.ClusterActorRefProvider; -import akka.event.Logging; -import akka.event.LoggingAdapter; import com.google.common.annotations.VisibleForTesting; import java.util.HashMap; import java.util.Map; @@ -29,6 +27,8 @@ import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketSto import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.GetBucketsByMembersReply; import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.UpdateRemoteBuckets; import org.opendaylight.controller.utils.ConditionalProbe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A store that syncs its data across nodes in the cluster. @@ -43,7 +43,7 @@ public class BucketStore> extends AbstractUntypedActorWithMe private static final Long NO_VERSION = -1L; - final LoggingAdapter log = Logging.getLogger(getContext().system(), this); + protected final Logger log = LoggerFactory.getLogger(getClass()); /** * Bucket owned by the node diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/Gossiper.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/Gossiper.java index 1bbcc69f5e..8af1c83c55 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/Gossiper.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/Gossiper.java @@ -17,14 +17,7 @@ import akka.cluster.ClusterActorRefProvider; import akka.cluster.ClusterEvent; import akka.cluster.Member; import akka.dispatch.Mapper; -import akka.event.Logging; -import akka.event.LoggingAdapter; import akka.pattern.Patterns; -import org.opendaylight.controller.cluster.common.actor.AbstractUntypedActorWithMetering; -import org.opendaylight.controller.remote.rpc.RemoteRpcProviderConfig; -import scala.concurrent.Future; -import scala.concurrent.duration.FiniteDuration; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -32,15 +25,20 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; - -import static org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.GetBucketVersions; -import static org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.GetBucketVersionsReply; -import static org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.GetBucketsByMembers; -import static org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.GetBucketsByMembersReply; -import static org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.UpdateRemoteBuckets; -import static org.opendaylight.controller.remote.rpc.registry.gossip.Messages.GossiperMessages.GossipEnvelope; -import static org.opendaylight.controller.remote.rpc.registry.gossip.Messages.GossiperMessages.GossipStatus; -import static org.opendaylight.controller.remote.rpc.registry.gossip.Messages.GossiperMessages.GossipTick; +import org.opendaylight.controller.cluster.common.actor.AbstractUntypedActorWithMetering; +import org.opendaylight.controller.remote.rpc.RemoteRpcProviderConfig; +import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.GetBucketVersions; +import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.GetBucketVersionsReply; +import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.GetBucketsByMembers; +import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.GetBucketsByMembersReply; +import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.BucketStoreMessages.UpdateRemoteBuckets; +import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.GossiperMessages.GossipEnvelope; +import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.GossiperMessages.GossipStatus; +import org.opendaylight.controller.remote.rpc.registry.gossip.Messages.GossiperMessages.GossipTick; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.concurrent.Future; +import scala.concurrent.duration.FiniteDuration; /** * Gossiper that syncs bucket store across nodes in the cluster. @@ -61,7 +59,7 @@ import static org.opendaylight.controller.remote.rpc.registry.gossip.Messages.Go public class Gossiper extends AbstractUntypedActorWithMetering { - final LoggingAdapter log = Logging.getLogger(getContext().system(), this); + private final Logger log = LoggerFactory.getLogger(getClass()); private Cluster cluster; @@ -121,30 +119,29 @@ public class Gossiper extends AbstractUntypedActorWithMetering { @Override public void postStop(){ - if (cluster != null) + if (cluster != null) { cluster.unsubscribe(getSelf()); - if (gossipTask != null) + } + if (gossipTask != null) { gossipTask.cancel(); + } } @Override protected void handleReceive(Object message) throws Exception { //Usually sent by self via gossip task defined above. But its not enforced. //These ticks can be sent by another actor as well which is esp. useful while testing - if (message instanceof GossipTick) + if (message instanceof GossipTick) { receiveGossipTick(); - - //Message from remote gossiper with its bucket versions - else if (message instanceof GossipStatus) + } else if (message instanceof GossipStatus) { + // Message from remote gossiper with its bucket versions receiveGossipStatus((GossipStatus) message); - - //Message from remote gossiper with buckets. This is usually in response to GossipStatus message - //The contained buckets are newer as determined by the remote gossiper by comparing the GossipStatus - //message with its local versions - else if (message instanceof GossipEnvelope) + } else if (message instanceof GossipEnvelope) { + // Message from remote gossiper with buckets. This is usually in response to GossipStatus + // message. The contained buckets are newer as determined by the remote gossiper by + // comparing the GossipStatus message with its local versions. receiveGossip((GossipEnvelope) message); - - else if (message instanceof ClusterEvent.MemberUp) { + } else if (message instanceof ClusterEvent.MemberUp) { receiveMemberUp(((ClusterEvent.MemberUp) message).member()); } else if (message instanceof ClusterEvent.MemberRemoved) { @@ -153,8 +150,9 @@ public class Gossiper extends AbstractUntypedActorWithMetering { } else if ( message instanceof ClusterEvent.UnreachableMember){ receiveMemberRemoveOrUnreachable(((ClusterEvent.UnreachableMember) message).member()); - } else + } else { unhandled(message); + } } /** @@ -181,11 +179,13 @@ public class Gossiper extends AbstractUntypedActorWithMetering { */ void receiveMemberUp(Member member) { - if (selfAddress.equals(member.address())) + if (selfAddress.equals(member.address())) { return; //ignore up notification for self + } - if (!clusterMembers.contains(member.address())) + if (!clusterMembers.contains(member.address())) { clusterMembers.add(member.address()); + } if(log.isDebugEnabled()) { log.debug("Added member [{}], Active member list [{}]", member.address(), clusterMembers); } @@ -198,13 +198,15 @@ public class Gossiper extends AbstractUntypedActorWithMetering { * 3. If there are more than one member, randomly pick one and send gossip status (bucket versions) to it. */ void receiveGossipTick(){ - if (clusterMembers.size() == 0) return; //no members to send gossip status to + if (clusterMembers.size() == 0) { + return; //no members to send gossip status to + } Address remoteMemberToGossipTo; - if (clusterMembers.size() == 1) + if (clusterMembers.size() == 1) { remoteMemberToGossipTo = clusterMembers.get(0); - else { + } else { Integer randomIndex = ThreadLocalRandom.current().nextInt(0, clusterMembers.size()); remoteMemberToGossipTo = clusterMembers.get(randomIndex); } @@ -229,8 +231,9 @@ public class Gossiper extends AbstractUntypedActorWithMetering { */ void receiveGossipStatus(GossipStatus status){ //Don't accept messages from non-members - if (!clusterMembers.contains(status.from())) + if (!clusterMembers.contains(status.from())) { return; + } final ActorRef sender = getSender(); Future futureReply = @@ -385,19 +388,23 @@ public class Gossiper extends AbstractUntypedActorWithMetering { for (Address address : remoteVersions.keySet()){ - if (localVersions.get(address) == null || remoteVersions.get(address) == null) + if (localVersions.get(address) == null || remoteVersions.get(address) == null) { continue; //this condition is taken care of by above diffs - if (localVersions.get(address) < remoteVersions.get(address)) + } + if (localVersions.get(address) < remoteVersions.get(address)) { localIsOlder.add(address); - else if (localVersions.get(address) > remoteVersions.get(address)) + } else if (localVersions.get(address) > remoteVersions.get(address)) { localIsNewer.add(address); + } } - if (!localIsOlder.isEmpty()) + if (!localIsOlder.isEmpty()) { sendGossipStatusTo(sender, localVersions ); + } - if (!localIsNewer.isEmpty()) + if (!localIsNewer.isEmpty()) { sendGossipTo(sender, localIsNewer);//send newer buckets to remote + } } return null; diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/Messages.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/Messages.java index b05bd7d0f6..00437e7e56 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/Messages.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/gossip/Messages.java @@ -46,7 +46,8 @@ public class Messages { } public static class ContainsBuckets implements Serializable{ - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -4940160367495308286L; + private final Map buckets; public ContainsBuckets(Map buckets){ @@ -87,7 +88,8 @@ public class Messages { } public static class ContainsBucketVersions implements Serializable{ - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -8172148925383801613L; + Map versions; public ContainsBucketVersions(Map versions) { @@ -119,15 +121,16 @@ public class Messages { public static class GossiperMessages{ public static class Tick implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -4770935099506366773L; } public static final class GossipTick extends Tick { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 5803354404380026143L; } public static final class GossipStatus extends ContainsBucketVersions implements Serializable{ - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = -593037395143883265L; + private final Address from; public GossipStatus(Address from, Map versions) { @@ -141,7 +144,8 @@ public class Messages { } public static final class GossipEnvelope extends ContainsBuckets implements Serializable { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 8346634072582438818L; + private final Address from; private final Address to; diff --git a/opendaylight/md-sal/sal-test-model/pom.xml b/opendaylight/md-sal/sal-test-model/pom.xml index 2a8a80da09..852e99e146 100644 --- a/opendaylight/md-sal/sal-test-model/pom.xml +++ b/opendaylight/md-sal/sal-test-model/pom.xml @@ -9,6 +9,9 @@ 4.0.0 + sal-test-model + bundle + org.opendaylight.yangtools @@ -20,7 +23,6 @@ - sal-test-model diff --git a/opendaylight/md-sal/sal-test-model/src/main/yang/opendaylight-of-migration-test-model.yang b/opendaylight/md-sal/sal-test-model/src/main/yang/opendaylight-of-migration-test-model.yang new file mode 100644 index 0000000000..24ff5dd965 --- /dev/null +++ b/opendaylight/md-sal/sal-test-model/src/main/yang/opendaylight-of-migration-test-model.yang @@ -0,0 +1,156 @@ +module opendaylight-of-migration-test-model { + + namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:of-migration-test-model"; + prefix of-migration-test; + + import opendaylight-mdsal-list-test {prefix test;} + import yang-ext {prefix ext;} + import opendaylight-mdsal-augment-test {prefix aug;} + import opendaylight-test-routed-rpc {prefix routed;} + + description + "This module contains a collection of YANG definitions used for + test cases that used to depend on flow model."; + + revision 2015-02-10 { + } + + typedef bit-flags { + type bits { + bit FLAG_ONE; + bit FLAG_TWO; + bit FLAG_THREE; + bit FLAG_FOUR; + bit FLAG_FIVE; + } + } + + typedef custom-enum { + type enumeration { + enum type1; + enum type2; + enum type3; + } + } + + grouping enum-grouping { + leaf attr-enum { + type custom-enum; + } + } + + grouping aug-grouping { + container cont1 { + leaf attr-str { + type string; + } + } + + container cont2 { + list contlist1 { + key "attr-str"; + + leaf attr-str { + type string; + } + + uses enum-grouping; + } + } + + leaf attr-str1 { + type string; + } + + leaf attr-str2 { + type string; + } + + leaf attr-str3 { + type string; + } + + leaf attr-str4 { + type string; + } + + list list1 { + key "attr-str"; + leaf attr-str { + type string; + } + + list list1-1 { + key "attr-int"; + leaf attr-int { + type int32; + } + + leaf attr-str { + type string; + } + + leaf flags { + type bit-flags; + } + } + + list list1-2 { + key "attr-int"; + leaf attr-int { + type int32; + } + + leaf attr-str { + type string; + } + } + } + } + + augment "/test:top/test:top-level-list" { + ext:augment-identifier tll-complex-augment; + uses aug-grouping; + } + + augment "/test:top/test:top-level-list/list1/list1-1" { + ext:augment-identifier list11-simple-augment; + + leaf attr-str2 { + type string; + } + + container cont { + leaf attr-int { + type int32; + } + } + } + + augment "/test:top/test:top-level-list/test:nested-list/" { + ext:augment-identifier nested-list-simple-augment; + + leaf type { + type string; + } + } + + rpc knock-knock { + input { + leaf knocker-id { + ext:context-reference routed:test-context; + type instance-identifier; + } + + leaf question { + type string; + } + } + + output { + leaf answer { + type string; + } + } + } +} diff --git a/opendaylight/md-sal/sal-test-model/src/main/yang/opendaylight-test-notification.yang b/opendaylight/md-sal/sal-test-model/src/main/yang/opendaylight-test-notification.yang new file mode 100644 index 0000000000..31ec7aed61 --- /dev/null +++ b/opendaylight/md-sal/sal-test-model/src/main/yang/opendaylight-test-notification.yang @@ -0,0 +1,25 @@ +module opendaylight-test-notification { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:test:bi:ba:notification"; + prefix "ntf"; + + description + "Test model for testing of registering notification listener and publishing of notification."; + + revision "2015-02-05" { + description + "Initial revision"; + } + + notification out-of-pixie-dust-notification { + description "Just a testing notification that we can not fly for now."; + + leaf reason { + type string; + } + + leaf days-till-new-dust { + type uint16; + } + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/topology-lldp-discovery/pom.xml b/opendaylight/md-sal/topology-lldp-discovery/pom.xml index cc684a58b2..70cd4b8702 100644 --- a/opendaylight/md-sal/topology-lldp-discovery/pom.xml +++ b/opendaylight/md-sal/topology-lldp-discovery/pom.xml @@ -12,7 +12,6 @@ bundle 2.4.0 - 14.0.1 2.5 diff --git a/opendaylight/netconf/config-netconf-connector/pom.xml b/opendaylight/netconf/config-netconf-connector/pom.xml index 2b3015243f..3a949697e9 100644 --- a/opendaylight/netconf/config-netconf-connector/pom.xml +++ b/opendaylight/netconf/config-netconf-connector/pom.xml @@ -29,6 +29,10 @@ ${project.groupId} netconf-mapping-api + + ${project.groupId} + netconf-notifications-api + ${project.groupId} netconf-util @@ -106,8 +110,6 @@ org.opendaylight.controller.netconf.confignetconfconnector.util, org.opendaylight.controller.netconf.confignetconfconnector.osgi, org.opendaylight.controller.netconf.confignetconfconnector.exception, - * - diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfig.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfig.java index f526d92895..ca6a8c46b9 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfig.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfig.java @@ -37,7 +37,7 @@ import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services; import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfigXmlParser.EditConfigExecution; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreSnapshot; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreContext; import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; import org.opendaylight.controller.netconf.util.xml.XmlElement; import org.opendaylight.controller.netconf.util.xml.XmlUtil; @@ -52,12 +52,12 @@ public class EditConfig extends AbstractConfigNetconfOperation { private static final Logger LOG = LoggerFactory.getLogger(EditConfig.class); - private final YangStoreSnapshot yangStoreSnapshot; + private final YangStoreContext yangStoreSnapshot; private final TransactionProvider transactionProvider; private EditConfigXmlParser editConfigXmlParser; - public EditConfig(YangStoreSnapshot yangStoreSnapshot, TransactionProvider transactionProvider, + public EditConfig(YangStoreContext yangStoreSnapshot, TransactionProvider transactionProvider, ConfigRegistryClient configRegistryClient, String netconfSessionIdForReporting) { super(configRegistryClient, netconfSessionIdForReporting); this.yangStoreSnapshot = yangStoreSnapshot; @@ -204,7 +204,7 @@ public class EditConfig extends AbstractConfigNetconfOperation { } } - public static Config getConfigMapping(ConfigRegistryClient configRegistryClient, YangStoreSnapshot yangStoreSnapshot) { + public static Config getConfigMapping(ConfigRegistryClient configRegistryClient, YangStoreContext yangStoreSnapshot) { Map> factories = transformMbeToModuleConfigs(configRegistryClient, yangStoreSnapshot.getModuleMXBeanEntryMap()); Map> identitiesMap = transformIdentities(yangStoreSnapshot.getModules()); diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/get/Get.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/get/Get.java index b504cbf6fd..27d53cdc32 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/get/Get.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/get/Get.java @@ -26,7 +26,7 @@ import org.opendaylight.controller.netconf.confignetconfconnector.mapping.runtim import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; import org.opendaylight.controller.netconf.confignetconfconnector.operations.Datastore; import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfig; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreSnapshot; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreContext; import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException; import org.opendaylight.controller.netconf.util.exception.UnexpectedElementException; import org.opendaylight.controller.netconf.util.exception.UnexpectedNamespaceException; @@ -38,10 +38,10 @@ import org.w3c.dom.Element; public class Get extends AbstractConfigNetconfOperation { - private final YangStoreSnapshot yangStoreSnapshot; + private final YangStoreContext yangStoreSnapshot; private static final Logger LOG = LoggerFactory.getLogger(Get.class); - public Get(YangStoreSnapshot yangStoreSnapshot, ConfigRegistryClient configRegistryClient, + public Get(YangStoreContext yangStoreSnapshot, ConfigRegistryClient configRegistryClient, String netconfSessionIdForReporting) { super(configRegistryClient, netconfSessionIdForReporting); this.yangStoreSnapshot = yangStoreSnapshot; diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/GetConfig.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/GetConfig.java index 2ff4dd677f..350ace5eb1 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/GetConfig.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/getconfig/GetConfig.java @@ -20,7 +20,7 @@ import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; import org.opendaylight.controller.netconf.confignetconfconnector.operations.Datastore; import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfig; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreSnapshot; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreContext; import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException; import org.opendaylight.controller.netconf.util.exception.UnexpectedElementException; @@ -36,14 +36,14 @@ public class GetConfig extends AbstractConfigNetconfOperation { public static final String GET_CONFIG = "get-config"; - private final YangStoreSnapshot yangStoreSnapshot; + private final YangStoreContext yangStoreSnapshot; private final Optional maybeNamespace; private final TransactionProvider transactionProvider; private static final Logger LOG = LoggerFactory.getLogger(GetConfig.class); - public GetConfig(YangStoreSnapshot yangStoreSnapshot, Optional maybeNamespace, + public GetConfig(YangStoreContext yangStoreSnapshot, Optional maybeNamespace, TransactionProvider transactionProvider, ConfigRegistryClient configRegistryClient, String netconfSessionIdForReporting) { super(configRegistryClient, netconfSessionIdForReporting); diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpc.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpc.java index 937a2ad588..ebbc0e5695 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpc.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/runtimerpc/RuntimeRpc.java @@ -30,7 +30,7 @@ import org.opendaylight.controller.netconf.confignetconfconnector.mapping.rpc.In import org.opendaylight.controller.netconf.confignetconfconnector.mapping.rpc.ModuleRpcs; import org.opendaylight.controller.netconf.confignetconfconnector.mapping.rpc.Rpcs; import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreSnapshot; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreContext; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException; import org.opendaylight.controller.netconf.util.xml.XmlElement; @@ -45,9 +45,9 @@ public class RuntimeRpc extends AbstractConfigNetconfOperation { private static final Logger LOG = LoggerFactory.getLogger(RuntimeRpc.class); public static final String CONTEXT_INSTANCE = "context-instance"; - private final YangStoreSnapshot yangStoreSnapshot; + private final YangStoreContext yangStoreSnapshot; - public RuntimeRpc(final YangStoreSnapshot yangStoreSnapshot, ConfigRegistryClient configRegistryClient, + public RuntimeRpc(final YangStoreContext yangStoreSnapshot, ConfigRegistryClient configRegistryClient, String netconfSessionIdForReporting) { super(configRegistryClient, netconfSessionIdForReporting); this.yangStoreSnapshot = yangStoreSnapshot; diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/Activator.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/Activator.java index faaa17d528..f39a21d16b 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/Activator.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/Activator.java @@ -12,6 +12,7 @@ import static com.google.common.base.Preconditions.checkState; import java.util.Dictionary; import java.util.Hashtable; +import org.opendaylight.controller.netconf.api.util.NetconfConstants; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; import org.osgi.framework.BundleActivator; @@ -43,7 +44,7 @@ public class Activator implements BundleActivator { SchemaContextProvider schemaContextProvider = reference.getBundle().getBundleContext().getService(reference); - YangStoreServiceImpl yangStoreService = new YangStoreServiceImpl(schemaContextProvider); + YangStoreService yangStoreService = new YangStoreService(schemaContextProvider, context); configRegistryLookup = new ConfigRegistryLookupThread(yangStoreService); configRegistryLookup.start(); return configRegistryLookup; @@ -79,9 +80,9 @@ public class Activator implements BundleActivator { } private class ConfigRegistryLookupThread extends Thread { - private final YangStoreServiceImpl yangStoreService; + private final YangStoreService yangStoreService; - private ConfigRegistryLookupThread(YangStoreServiceImpl yangStoreService) { + private ConfigRegistryLookupThread(YangStoreService yangStoreService) { super("config-registry-lookup"); this.yangStoreService = yangStoreService; } @@ -91,7 +92,7 @@ public class Activator implements BundleActivator { NetconfOperationServiceFactoryImpl factory = new NetconfOperationServiceFactoryImpl(yangStoreService); LOG.debug("Registering into OSGi"); Dictionary properties = new Hashtable<>(); - properties.put("name", "config-netconf-connector"); + properties.put(NetconfConstants.SERVICE_NAME, NetconfConstants.CONFIG_NETCONF_CONNECTOR); osgiRegistration = context.registerService(NetconfOperationServiceFactory.class, factory, properties); } } diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationProvider.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationProvider.java index 04d5d4bb6f..612bd85998 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationProvider.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationProvider.java @@ -27,7 +27,7 @@ import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; final class NetconfOperationProvider { private final Set operations; - NetconfOperationProvider(YangStoreSnapshot yangStoreSnapshot, ConfigRegistryClient configRegistryClient, + NetconfOperationProvider(YangStoreContext yangStoreSnapshot, ConfigRegistryClient configRegistryClient, TransactionProvider transactionProvider, String netconfSessionIdForReporting) { operations = setUpOperations(yangStoreSnapshot, configRegistryClient, transactionProvider, @@ -38,7 +38,7 @@ final class NetconfOperationProvider { return operations; } - private static Set setUpOperations(YangStoreSnapshot yangStoreSnapshot, + private static Set setUpOperations(YangStoreContext yangStoreSnapshot, ConfigRegistryClient configRegistryClient, TransactionProvider transactionProvider, String netconfSessionIdForReporting) { Set ops = Sets.newHashSet(); diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceFactoryImpl.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceFactoryImpl.java index b5ae66d605..590f4c4ceb 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceFactoryImpl.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceFactoryImpl.java @@ -8,10 +8,19 @@ package org.opendaylight.controller.netconf.confignetconfconnector.osgi; +import com.google.common.base.Optional; import java.lang.management.ManagementFactory; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import javax.management.MBeanServer; import org.opendaylight.controller.config.util.ConfigRegistryJMXClient; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.yangtools.yang.model.api.Module; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,10 +75,119 @@ public class NetconfOperationServiceFactoryImpl implements NetconfOperationServi @Override public NetconfOperationServiceImpl createService(String netconfSessionIdForReporting) { - try { - return new NetconfOperationServiceImpl(yangStoreService, jmxClient, netconfSessionIdForReporting); - } catch (YangStoreException e) { - throw new IllegalStateException(e); + return new NetconfOperationServiceImpl(yangStoreService, jmxClient, netconfSessionIdForReporting); + } + + + @Override + public Set getCapabilities() { + return setupCapabilities(yangStoreService); + } + + @Override + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + return yangStoreService.registerCapabilityListener(listener); + } + + public static Set setupCapabilities(final YangStoreContext yangStoreSnapshot) { + Set capabilities = new HashSet<>(); + // [RFC6241] 8.3. Candidate Configuration Capability + capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:candidate:1.0")); + + // TODO rollback on error not supported EditConfigXmlParser:100 + // [RFC6241] 8.5. Rollback-on-Error Capability + // capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:rollback-on-error:1.0")); + + Set modules = yangStoreSnapshot.getModules(); + for (Module module : modules) { + capabilities.add(new YangStoreCapability(module, yangStoreSnapshot.getModuleSource(module))); + } + + return capabilities; + } + + private static class BasicCapability implements Capability { + + private final String capability; + + private BasicCapability(final String capability) { + this.capability = capability; + } + + @Override + public String getCapabilityUri() { + return capability; + } + + @Override + public Optional getModuleNamespace() { + return Optional.absent(); + } + + @Override + public Optional getModuleName() { + return Optional.absent(); + } + + @Override + public Optional getRevision() { + return Optional.absent(); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.absent(); + } + + @Override + public Collection getLocation() { + return Collections.emptyList(); + } + + @Override + public String toString() { + return capability; + } + } + + static final class YangStoreCapability extends BasicCapability { + + private final String content; + private final String revision; + private final String moduleName; + private final String moduleNamespace; + + public YangStoreCapability(final Module module, final String moduleContent) { + super(toCapabilityURI(module)); + this.content = moduleContent; + this.moduleName = module.getName(); + this.moduleNamespace = module.getNamespace().toString(); + this.revision = Util.writeDate(module.getRevision()); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.of(content); + } + + private static String toCapabilityURI(final Module module) { + return String.valueOf(module.getNamespace()) + "?module=" + + module.getName() + "&revision=" + Util.writeDate(module.getRevision()); + } + + @Override + public Optional getModuleName() { + return Optional.of(moduleName); + } + + @Override + public Optional getModuleNamespace() { + return Optional.of(moduleNamespace); + } + + @Override + public Optional getRevision() { + return Optional.of(revision); } } } diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java index 902be44fd9..28001851cc 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImpl.java @@ -8,81 +8,26 @@ package org.opendaylight.controller.netconf.confignetconfconnector.osgi; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.collect.Sets; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; import java.util.Set; -import org.opendaylight.controller.config.api.LookupRegistry; import org.opendaylight.controller.config.util.ConfigRegistryJMXClient; -import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; -import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; -import org.opendaylight.controller.netconf.mapping.api.Capability; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; -import org.opendaylight.yangtools.yang.model.api.Module; /** - * Manages life cycle of {@link YangStoreSnapshot}. + * Manages life cycle of {@link YangStoreContext}. */ public class NetconfOperationServiceImpl implements NetconfOperationService { - private final YangStoreSnapshot yangStoreSnapshot; private final NetconfOperationProvider operationProvider; - private final Set capabilities; private final TransactionProvider transactionProvider; public NetconfOperationServiceImpl(final YangStoreService yangStoreService, final ConfigRegistryJMXClient jmxClient, - final String netconfSessionIdForReporting) throws YangStoreException { - - yangStoreSnapshot = yangStoreService.getYangStoreSnapshot(); - checkConsistencyBetweenYangStoreAndConfig(jmxClient, yangStoreSnapshot); + final String netconfSessionIdForReporting) { transactionProvider = new TransactionProvider(jmxClient, netconfSessionIdForReporting); - operationProvider = new NetconfOperationProvider(yangStoreSnapshot, jmxClient, transactionProvider, + operationProvider = new NetconfOperationProvider(yangStoreService, jmxClient, transactionProvider, netconfSessionIdForReporting); - capabilities = setupCapabilities(yangStoreSnapshot); - } - - - @VisibleForTesting - static void checkConsistencyBetweenYangStoreAndConfig(final LookupRegistry jmxClient, final YangStoreSnapshot yangStoreSnapshot) { - Set missingModulesFromConfig = Sets.newHashSet(); - - Set modulesSeenByConfig = jmxClient.getAvailableModuleFactoryQNames(); - Map> moduleMXBeanEntryMap = yangStoreSnapshot.getModuleMXBeanEntryMap(); - - for (Map moduleNameToMBE : moduleMXBeanEntryMap.values()) { - for (ModuleMXBeanEntry moduleMXBeanEntry : moduleNameToMBE.values()) { - String moduleSeenByYangStore = moduleMXBeanEntry.getYangModuleQName().toString(); - if(!modulesSeenByConfig.contains(moduleSeenByYangStore)){ - missingModulesFromConfig.add(moduleSeenByYangStore); - } - } - } - - Preconditions - .checkState( - missingModulesFromConfig.isEmpty(), - "There are inconsistencies between configuration subsystem and yangstore in terms of discovered yang modules, yang modules missing from config subsystem but present in yangstore: %s, %sAll modules present in config: %s", - missingModulesFromConfig, System.lineSeparator(), modulesSeenByConfig); - - } - - @Override - public void close() { - yangStoreSnapshot.close(); - transactionProvider.close(); - } - - @Override - public Set getCapabilities() { - return capabilities; } @Override @@ -90,105 +35,9 @@ public class NetconfOperationServiceImpl implements NetconfOperationService { return operationProvider.getOperations(); } - private static Set setupCapabilities(final YangStoreSnapshot yangStoreSnapshot) { - Set capabilities = new HashSet<>(); - // [RFC6241] 8.3. Candidate Configuration Capability - capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:candidate:1.0")); - - // TODO rollback on error not supported EditConfigXmlParser:100 - // [RFC6241] 8.5. Rollback-on-Error Capability - // capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:rollback-on-error:1.0")); - - Set modules = yangStoreSnapshot.getModules(); - for (Module module : modules) { - capabilities.add(new YangStoreCapability(module, yangStoreSnapshot.getModuleSource(module))); - } - - return capabilities; - } - - private static class BasicCapability implements Capability { - - private final String capability; - - private BasicCapability(final String capability) { - this.capability = capability; - } - - @Override - public String getCapabilityUri() { - return capability; - } - - @Override - public Optional getModuleNamespace() { - return Optional.absent(); - } - - @Override - public Optional getModuleName() { - return Optional.absent(); - } - - @Override - public Optional getRevision() { - return Optional.absent(); - } - - @Override - public Optional getCapabilitySchema() { - return Optional.absent(); - } - - @Override - public Collection getLocation() { - return Collections.emptyList(); - } - - @Override - public String toString() { - return capability; - } + @Override + public void close() { + transactionProvider.close(); } - private static final class YangStoreCapability extends BasicCapability { - - private final String content; - private final String revision; - private final String moduleName; - private final String moduleNamespace; - - public YangStoreCapability(final Module module, final String moduleContent) { - super(toCapabilityURI(module)); - this.content = moduleContent; - this.moduleName = module.getName(); - this.moduleNamespace = module.getNamespace().toString(); - this.revision = Util.writeDate(module.getRevision()); - } - - @Override - public Optional getCapabilitySchema() { - return Optional.of(content); - } - - private static String toCapabilityURI(final Module module) { - return String.valueOf(module.getNamespace()) + "?module=" - + module.getName() + "&revision=" + Util.writeDate(module.getRevision()); - } - - @Override - public Optional getModuleName() { - return Optional.of(moduleName); - } - - @Override - public Optional getModuleNamespace() { - return Optional.of(moduleNamespace); - } - - @Override - public Optional getRevision() { - return Optional.of(revision); - } - } } diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreContext.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreContext.java new file mode 100644 index 0000000000..6a38a9ad3d --- /dev/null +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreContext.java @@ -0,0 +1,38 @@ +/* + * 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.confignetconfconnector.osgi; + +import java.util.Map; +import java.util.Set; +import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier; + +public interface YangStoreContext { + + /** + * @deprecated Use {@link #getQNamesToIdentitiesToModuleMXBeanEntries()} instead. This method return only one + * module representation even if multiple revisions are available. + */ + @Deprecated + Map> getModuleMXBeanEntryMap(); + + + Map> getQNamesToIdentitiesToModuleMXBeanEntries(); + + /** + * Get all modules discovered when this snapshot was created. + * @return all modules discovered. If one module exists with two different revisions, return both. + */ + Set getModules(); + + String getModuleSource(ModuleIdentifier moduleIdentifier); + +} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreException.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreException.java deleted file mode 100644 index 18558b39ca..0000000000 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreException.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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.confignetconfconnector.osgi; - -public class YangStoreException extends Exception { - - private static final long serialVersionUID = 2841238836278528836L; - - public YangStoreException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreService.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreService.java index 969d7cfdb3..176800fb97 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreService.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreService.java @@ -5,18 +5,261 @@ * 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.confignetconfconnector.osgi; -/** - * Yang store OSGi service - */ -public interface YangStoreService { +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicReference; +import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.netconf.notifications.BaseNetconfNotificationListener; +import org.opendaylight.controller.netconf.notifications.BaseNotificationPublisherRegistration; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationCollector; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChangeBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.changed.by.parms.ChangedByBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.changed.by.parms.changed.by.server.or.user.ServerBuilder; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier; +import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class YangStoreService implements YangStoreContext { + + private static final Logger LOG = LoggerFactory.getLogger(YangStoreService.class); /** - * Module entry objects mapped to module names and namespaces. + * This is a rather interesting locking model. We need to guard against both the + * cache expiring from GC and being invalidated by schema context change. The + * context can change while we are doing processing, so we do not want to block + * it, so no synchronization can happen on the methods. + * + * So what we are doing is the following: + * + * We synchronize with GC as usual, using a SoftReference. * - * @return actual view of what is available in OSGi service registry. + * The atomic reference is used to synchronize with {@link #refresh()}, e.g. when + * refresh happens, it will push a SoftReference(null), e.g. simulate the GC. Now + * that may happen while the getter is already busy acting on the old schema context, + * so it needs to understand that a refresh has happened and retry. To do that, it + * attempts a CAS operation -- if it fails, in knows that the SoftReference has + * been replaced and thus it needs to retry. + * + * Note that {@link #getYangStoreSnapshot()} will still use synchronize() internally + * to stop multiple threads doing the same work. + */ + private final AtomicReference> ref = + new AtomicReference<>(new SoftReference(null)); + + private final SchemaContextProvider schemaContextProvider; + private final BaseNetconfNotificationListener notificationPublisher; + + private final ExecutorService notificationExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(final Runnable r) { + return new Thread(r, "config-netconf-connector-capability-notifications"); + } + }); + + private final Set listeners = Collections.synchronizedSet(new HashSet()); + + public YangStoreService(final SchemaContextProvider schemaContextProvider, final BundleContext context) { + this(schemaContextProvider, new NotificationCollectorTracker(context)); + } + + public YangStoreService(final SchemaContextProvider schemaContextProvider, final BaseNetconfNotificationListener notificationHandler) { + this.schemaContextProvider = schemaContextProvider; + this.notificationPublisher = notificationHandler; + } + + private synchronized YangStoreContext getYangStoreSnapshot() { + SoftReference r = ref.get(); + YangStoreSnapshot ret = r.get(); + + while (ret == null) { + // We need to be compute a new value + ret = new YangStoreSnapshot(schemaContextProvider.getSchemaContext()); + + if (!ref.compareAndSet(r, new SoftReference<>(ret))) { + LOG.debug("Concurrent refresh detected, recomputing snapshot"); + r = ref.get(); + ret = null; + } + } + + return ret; + } + + @Override + public Map> getModuleMXBeanEntryMap() { + return getYangStoreSnapshot().getModuleMXBeanEntryMap(); + } + + @Override + public Map> getQNamesToIdentitiesToModuleMXBeanEntries() { + return getYangStoreSnapshot().getQNamesToIdentitiesToModuleMXBeanEntries(); + } + + @Override + public Set getModules() { + return getYangStoreSnapshot().getModules(); + } + + @Override + public String getModuleSource(final ModuleIdentifier moduleIdentifier) { + return getYangStoreSnapshot().getModuleSource(moduleIdentifier); + } + + public void refresh() { + final YangStoreSnapshot previous = ref.get().get(); + ref.set(new SoftReference(null)); + notificationExecutor.submit(new CapabilityChangeNotifier(previous)); + } + + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + if(ref.get() == null || ref.get().get() == null) { + getYangStoreSnapshot(); + } + + this.listeners.add(listener); + listener.onCapabilitiesAdded(NetconfOperationServiceFactoryImpl.setupCapabilities(ref.get().get())); + + return new AutoCloseable() { + @Override + public void close() throws Exception { + YangStoreService.this.listeners.remove(listener); + } + }; + } + + private static final Function MODULE_TO_CAPABILITY = new Function() { + @Override + public Capability apply(final Module module) { + return new NetconfOperationServiceFactoryImpl.YangStoreCapability(module, module.getSource()); + } + }; + + private final class CapabilityChangeNotifier implements Runnable { + + private final YangStoreSnapshot previous; + + public CapabilityChangeNotifier(final YangStoreSnapshot previous) { + this.previous = previous; + } + + @Override + public void run() { + final YangStoreContext current = getYangStoreSnapshot(); + + if(current.equals(previous) == false) { + final Sets.SetView removed = Sets.difference(previous.getModules(), current.getModules()); + final Sets.SetView added = Sets.difference(current.getModules(), previous.getModules()); + + // Notify notification manager + notificationPublisher.onCapabilityChanged(computeDiff(removed, added)); + + // Notify direct capability listener TODO would it not be better if the capability listeners went through notification manager ? + for (final CapabilityListener listener : listeners) { + listener.onCapabilitiesAdded(Sets.newHashSet(Collections2.transform(added, MODULE_TO_CAPABILITY))); + } + for (final CapabilityListener listener : listeners) { + listener.onCapabilitiesRemoved(Sets.newHashSet(Collections2.transform(removed, MODULE_TO_CAPABILITY))); + } + } + } + } + + private static final Function MODULE_TO_URI = new Function() { + @Override + public Uri apply(final Module input) { + return new Uri(new NetconfOperationServiceFactoryImpl.YangStoreCapability(input, input.getSource()).getCapabilityUri()); + } + }; + + static NetconfCapabilityChange computeDiff(final Sets.SetView removed, final Sets.SetView added) { + final NetconfCapabilityChangeBuilder netconfCapabilityChangeBuilder = new NetconfCapabilityChangeBuilder(); + netconfCapabilityChangeBuilder.setChangedBy(new ChangedByBuilder().setServerOrUser(new ServerBuilder().setServer(true).build()).build()); + netconfCapabilityChangeBuilder.setDeletedCapability(Lists.newArrayList(Collections2.transform(removed, MODULE_TO_URI))); + netconfCapabilityChangeBuilder.setAddedCapability(Lists.newArrayList(Collections2.transform(added, MODULE_TO_URI))); + // TODO modified should be computed ... but why ? + netconfCapabilityChangeBuilder.setModifiedCapability(Collections.emptyList()); + return netconfCapabilityChangeBuilder.build(); + } + + + /** + * Looks for NetconfNotificationCollector service and publishes base netconf notifications if possible */ - YangStoreSnapshot getYangStoreSnapshot() throws YangStoreException; + private static class NotificationCollectorTracker implements ServiceTrackerCustomizer, BaseNetconfNotificationListener, AutoCloseable { + + private final BundleContext context; + private final ServiceTracker listenerTracker; + private BaseNotificationPublisherRegistration publisherReg; + + public NotificationCollectorTracker(final BundleContext context) { + this.context = context; + listenerTracker = new ServiceTracker<>(context, NetconfNotificationCollector.class, this); + listenerTracker.open(); + } + + @Override + public synchronized NetconfNotificationCollector addingService(final ServiceReference reference) { + closePublisherRegistration(); + publisherReg = context.getService(reference).registerBaseNotificationPublisher(); + return null; + } + + @Override + public synchronized void modifiedService(final ServiceReference reference, final NetconfNotificationCollector service) { + closePublisherRegistration(); + publisherReg = context.getService(reference).registerBaseNotificationPublisher(); + } + + @Override + public synchronized void removedService(final ServiceReference reference, final NetconfNotificationCollector service) { + closePublisherRegistration(); + publisherReg = null; + } + + private void closePublisherRegistration() { + if(publisherReg != null) { + publisherReg.close(); + } + } + + @Override + public synchronized void close() { + closePublisherRegistration(); + listenerTracker.close(); + } + + @Override + public void onCapabilityChanged(final NetconfCapabilityChange capabilityChange) { + if(publisherReg == null) { + LOG.warn("Omitting notification due to missing notification service: {}", capabilityChange); + return; + } + publisherReg.onCapabilityChanged(capabilityChange); + } + } } diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreServiceImpl.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreServiceImpl.java deleted file mode 100644 index 958af54e3f..0000000000 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreServiceImpl.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.confignetconfconnector.osgi; - -import java.lang.ref.SoftReference; -import java.util.concurrent.atomic.AtomicReference; -import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class YangStoreServiceImpl implements YangStoreService { - private static final Logger LOG = LoggerFactory.getLogger(YangStoreServiceImpl.class); - - /** - * This is a rather interesting locking model. We need to guard against both the - * cache expiring from GC and being invalidated by schema context change. The - * context can change while we are doing processing, so we do not want to block - * it, so no synchronization can happen on the methods. - * - * So what we are doing is the following: - * - * We synchronize with GC as usual, using a SoftReference. - * - * The atomic reference is used to synchronize with {@link #refresh()}, e.g. when - * refresh happens, it will push a SoftReference(null), e.g. simulate the GC. Now - * that may happen while the getter is already busy acting on the old schema context, - * so it needs to understand that a refresh has happened and retry. To do that, it - * attempts a CAS operation -- if it fails, in knows that the SoftReference has - * been replaced and thus it needs to retry. - * - * Note that {@link #getYangStoreSnapshot()} will still use synchronize() internally - * to stop multiple threads doing the same work. - */ - private final AtomicReference> ref = new AtomicReference<>(new SoftReference(null)); - private final SchemaContextProvider service; - - public YangStoreServiceImpl(final SchemaContextProvider service) { - this.service = service; - } - - @Override - public synchronized YangStoreSnapshotImpl getYangStoreSnapshot() throws YangStoreException { - SoftReference r = ref.get(); - YangStoreSnapshotImpl ret = r.get(); - - while (ret == null) { - // We need to be compute a new value - ret = new YangStoreSnapshotImpl(service.getSchemaContext()); - - if (!ref.compareAndSet(r, new SoftReference<>(ret))) { - LOG.debug("Concurrent refresh detected, recomputing snapshot"); - r = ref.get(); - ret = null; - } - } - - return ret; - } - - /** - * Called when schema context changes, invalidates cache. - */ - public void refresh() { - ref.set(new SoftReference(null)); - } -} diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreSnapshot.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreSnapshot.java index 8ec4fddbd4..0d3370548a 100644 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreSnapshot.java +++ b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreSnapshot.java @@ -5,36 +5,123 @@ * 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.confignetconfconnector.osgi; +import com.google.common.collect.Maps; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; +import org.opendaylight.controller.config.yangjmxgenerator.PackageTranslator; +import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry; +import org.opendaylight.controller.config.yangjmxgenerator.TypeProviderWrapper; +import org.opendaylight.yangtools.sal.binding.yang.types.TypeProviderImpl; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; -import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class YangStoreSnapshot implements YangStoreContext { + private static final Logger LOG = LoggerFactory.getLogger(YangStoreSnapshot.class); + + + private final Map> moduleMXBeanEntryMap; + + + private final Map> qNamesToIdentitiesToModuleMXBeanEntries; + + private final SchemaContext schemaContext; + + public YangStoreSnapshot(final SchemaContext resolveSchemaContext) { + LOG.trace("Resolved modules:{}", resolveSchemaContext.getModules()); + this.schemaContext = resolveSchemaContext; + // JMX generator + + Map namespaceToPackageMapping = Maps.newHashMap(); + PackageTranslator packageTranslator = new PackageTranslator(namespaceToPackageMapping); + Map qNamesToSIEs = new HashMap<>(); + Map knownSEITracker = new HashMap<>(); + // create SIE structure qNamesToSIEs + for (Module module : resolveSchemaContext.getModules()) { + String packageName = packageTranslator.getPackageName(module); + Map namesToSIEntries = ServiceInterfaceEntry + .create(module, packageName, knownSEITracker); + for (Entry sieEntry : namesToSIEntries.entrySet()) { + // merge value into qNamesToSIEs + if (qNamesToSIEs.containsKey(sieEntry.getKey()) == false) { + qNamesToSIEs.put(sieEntry.getKey(), sieEntry.getValue()); + } else { + throw new IllegalStateException("Cannot add two SIE with same qname " + + sieEntry.getValue()); + } + } + } + + Map> moduleMXBeanEntryMap = Maps.newHashMap(); + + Map> qNamesToIdentitiesToModuleMXBeanEntries = new HashMap<>(); + -public interface YangStoreSnapshot extends AutoCloseable { + for (Module module : schemaContext.getModules()) { + String packageName = packageTranslator.getPackageName(module); + TypeProviderWrapper typeProviderWrapper = new TypeProviderWrapper( + new TypeProviderImpl(resolveSchemaContext)); - /** - * @deprecated Use {@link #getQNamesToIdentitiesToModuleMXBeanEntries()} instead. This method return only one - * module representation even if multiple revisions are available. - */ - @Deprecated - Map> getModuleMXBeanEntryMap(); + QName qName = QName.create(module.getNamespace(), module.getRevision(), module.getName()); + Map namesToMBEs = + Collections.unmodifiableMap(ModuleMXBeanEntry.create(module, qNamesToSIEs, resolveSchemaContext, + typeProviderWrapper, packageName)); + moduleMXBeanEntryMap.put(module.getNamespace().toString(), namesToMBEs); + + qNamesToIdentitiesToModuleMXBeanEntries.put(qName, namesToMBEs); + } + this.moduleMXBeanEntryMap = Collections.unmodifiableMap(moduleMXBeanEntryMap); + this.qNamesToIdentitiesToModuleMXBeanEntries = Collections.unmodifiableMap(qNamesToIdentitiesToModuleMXBeanEntries); + + } + + @Override + public Map> getModuleMXBeanEntryMap() { + return moduleMXBeanEntryMap; + } + + @Override + public Map> getQNamesToIdentitiesToModuleMXBeanEntries() { + return qNamesToIdentitiesToModuleMXBeanEntries; + } + + @Override + public Set getModules() { + return schemaContext.getModules(); + } + + @Override + public String getModuleSource(final org.opendaylight.yangtools.yang.model.api.ModuleIdentifier moduleIdentifier) { + return schemaContext.getModuleSource(moduleIdentifier).get(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; - Map> getQNamesToIdentitiesToModuleMXBeanEntries(); + final YangStoreSnapshot that = (YangStoreSnapshot) o; - /** - * Get all modules discovered when this snapshot was created. - * @return all modules discovered. If one module exists with two different revisions, return both. - */ - Set getModules(); + if (schemaContext != null ? !schemaContext.equals(that.schemaContext) : that.schemaContext != null) + return false; - String getModuleSource(ModuleIdentifier moduleIdentifier); + return true; + } @Override - void close(); + public int hashCode() { + return schemaContext != null ? schemaContext.hashCode() : 0; + } } diff --git a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreSnapshotImpl.java b/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreSnapshotImpl.java deleted file mode 100644 index 075ae63343..0000000000 --- a/opendaylight/netconf/config-netconf-connector/src/main/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/YangStoreSnapshotImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.confignetconfconnector.osgi; - -import com.google.common.collect.Maps; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; -import org.opendaylight.controller.config.yangjmxgenerator.PackageTranslator; -import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry; -import org.opendaylight.controller.config.yangjmxgenerator.TypeProviderWrapper; -import org.opendaylight.yangtools.sal.binding.yang.types.TypeProviderImpl; -import org.opendaylight.yangtools.yang.common.QName; -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.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class YangStoreSnapshotImpl implements YangStoreSnapshot { - private static final Logger LOG = LoggerFactory.getLogger(YangStoreSnapshotImpl.class); - - - private final Map> moduleMXBeanEntryMap; - - - private final Map> qNamesToIdentitiesToModuleMXBeanEntries; - - private final SchemaContext schemaContext; - - - public YangStoreSnapshotImpl(final SchemaContext resolveSchemaContext) { - LOG.trace("Resolved modules:{}", resolveSchemaContext.getModules()); - this.schemaContext = resolveSchemaContext; - // JMX generator - - Map namespaceToPackageMapping = Maps.newHashMap(); - PackageTranslator packageTranslator = new PackageTranslator(namespaceToPackageMapping); - Map qNamesToSIEs = new HashMap<>(); - Map knownSEITracker = new HashMap<>(); - // create SIE structure qNamesToSIEs - for (Module module : resolveSchemaContext.getModules()) { - String packageName = packageTranslator.getPackageName(module); - Map namesToSIEntries = ServiceInterfaceEntry - .create(module, packageName, knownSEITracker); - for (Entry sieEntry : namesToSIEntries.entrySet()) { - // merge value into qNamesToSIEs - if (qNamesToSIEs.containsKey(sieEntry.getKey()) == false) { - qNamesToSIEs.put(sieEntry.getKey(), sieEntry.getValue()); - } else { - throw new IllegalStateException("Cannot add two SIE with same qname " - + sieEntry.getValue()); - } - } - } - - Map> moduleMXBeanEntryMap = Maps.newHashMap(); - - Map> qNamesToIdentitiesToModuleMXBeanEntries = new HashMap<>(); - - - for (Module module : schemaContext.getModules()) { - String packageName = packageTranslator.getPackageName(module); - TypeProviderWrapper typeProviderWrapper = new TypeProviderWrapper( - new TypeProviderImpl(resolveSchemaContext)); - - QName qName = QName.create(module.getNamespace(), module.getRevision(), module.getName()); - - Map namesToMBEs = - Collections.unmodifiableMap(ModuleMXBeanEntry.create(module, qNamesToSIEs, resolveSchemaContext, - typeProviderWrapper, packageName)); - moduleMXBeanEntryMap.put(module.getNamespace().toString(), namesToMBEs); - - qNamesToIdentitiesToModuleMXBeanEntries.put(qName, namesToMBEs); - } - this.moduleMXBeanEntryMap = Collections.unmodifiableMap(moduleMXBeanEntryMap); - this.qNamesToIdentitiesToModuleMXBeanEntries = Collections.unmodifiableMap(qNamesToIdentitiesToModuleMXBeanEntries); - - } - - @Override - public Map> getModuleMXBeanEntryMap() { - return moduleMXBeanEntryMap; - } - - @Override - public Map> getQNamesToIdentitiesToModuleMXBeanEntries() { - return qNamesToIdentitiesToModuleMXBeanEntries; - } - - @Override - public Set getModules() { - return schemaContext.getModules(); - } - - @Override - public String getModuleSource(final org.opendaylight.yangtools.yang.model.api.ModuleIdentifier moduleIdentifier) { - return schemaContext.getModuleSource(moduleIdentifier).get(); - } - - @Override - public void close() { - - } -} diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java index 6f9a62af1a..b1222997fb 100644 --- a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java @@ -13,6 +13,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -89,12 +91,12 @@ import org.opendaylight.controller.netconf.confignetconfconnector.operations.edi import org.opendaylight.controller.netconf.confignetconfconnector.operations.get.Get; import org.opendaylight.controller.netconf.confignetconfconnector.operations.getconfig.GetConfig; import org.opendaylight.controller.netconf.confignetconfconnector.operations.runtimerpc.RuntimeRpc; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreServiceImpl; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreSnapshot; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreContext; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreService; import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCloseSession; +import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory; import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouter; -import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceSnapshotImpl; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; @@ -109,6 +111,9 @@ import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; +import org.osgi.framework.Filter; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -132,25 +137,35 @@ public class NetconfMappingTest extends AbstractConfigTest { private TestImplModuleFactory factory4; @Mock - YangStoreSnapshot yangStoreSnapshot; + YangStoreContext yangStoreSnapshot; @Mock NetconfOperationRouter netconfOperationRouter; @Mock - NetconfOperationServiceSnapshotImpl netconfOperationServiceSnapshot; + AggregatedNetconfOperationServiceFactory netconfOperationServiceSnapshot; + @Mock + private AutoCloseable sessionCloseable; private TransactionProvider transactionProvider; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + + + final Filter filter = mock(Filter.class); + doReturn(filter).when(mockedContext).createFilter(anyString()); + doNothing().when(mockedContext).addServiceListener(any(ServiceListener.class), anyString()); + doReturn(new ServiceReference[]{}).when(mockedContext).getServiceReferences(anyString(), anyString()); + doReturn(getMbes()).when(this.yangStoreSnapshot).getModuleMXBeanEntryMap(); doReturn(getModules()).when(this.yangStoreSnapshot).getModules(); - doNothing().when(netconfOperationServiceSnapshot).close(); this.factory = new NetconfTestImplModuleFactory(); this.factory2 = new DepTestImplModuleFactory(); this.factory3 = new IdentityTestModuleFactory(); factory4 = new TestImplModuleFactory(); + doNothing().when(sessionCloseable).close(); + super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(mockedContext, this.factory, this.factory2, this.factory3, factory4)); @@ -362,7 +377,7 @@ public class NetconfMappingTest extends AbstractConfigTest { edit("netconfMessages/editConfig_none.xml"); closeSession(); - verify(netconfOperationServiceSnapshot).close(); + verify(sessionCloseable).close(); verifyNoMoreInteractions(netconfOperationRouter); verifyNoMoreInteractions(netconfOperationServiceSnapshot); } @@ -376,7 +391,7 @@ public class NetconfMappingTest extends AbstractConfigTest { private void closeSession() throws NetconfDocumentedException, ParserConfigurationException, SAXException, IOException { - DefaultCloseSession closeOp = new DefaultCloseSession(NETCONF_SESSION_ID, netconfOperationServiceSnapshot); + DefaultCloseSession closeOp = new DefaultCloseSession(NETCONF_SESSION_ID, sessionCloseable); executeOp(closeOp, "netconfMessages/closeSession.xml"); } @@ -629,13 +644,13 @@ public class NetconfMappingTest extends AbstractConfigTest { YangParserImpl yangParser = new YangParserImpl(); final SchemaContext schemaContext = yangParser.resolveSchemaContext(new HashSet<>(yangParser.parseYangModelsFromStreamsMapped(yangDependencies).values())); - YangStoreServiceImpl yangStoreService = new YangStoreServiceImpl(new SchemaContextProvider() { + YangStoreService yangStoreService = new YangStoreService(new SchemaContextProvider() { @Override public SchemaContext getSchemaContext() { return schemaContext ; } - }); - mBeanEntries.putAll(yangStoreService.getYangStoreSnapshot().getModuleMXBeanEntryMap()); + }, mockedContext); + mBeanEntries.putAll(yangStoreService.getModuleMXBeanEntryMap()); return mBeanEntries; } diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigTest.java index 817bedf4e2..ad57f897e0 100644 --- a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigTest.java +++ b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/operations/editconfig/EditConfigTest.java @@ -41,14 +41,14 @@ import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config import org.opendaylight.controller.netconf.confignetconfconnector.mapping.config.Services; import org.opendaylight.controller.netconf.confignetconfconnector.operations.ValidateTest; import org.opendaylight.controller.netconf.confignetconfconnector.operations.editconfig.EditConfigXmlParser.EditConfigExecution; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreSnapshot; +import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreContext; import org.opendaylight.controller.netconf.confignetconfconnector.transactions.TransactionProvider; import org.opendaylight.controller.netconf.util.xml.XmlUtil; public class EditConfigTest { @Mock - private YangStoreSnapshot yangStoreSnapshot; + private YangStoreContext yangStoreSnapshot; @Mock private TransactionProvider provider; @Mock diff --git a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImplTest.java b/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImplTest.java deleted file mode 100644 index 413aa5cc67..0000000000 --- a/opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/osgi/NetconfOperationServiceImplTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.confignetconfconnector.osgi; - -import static org.junit.Assert.fail; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; - -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import java.net.URI; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Map; -import java.util.Set; -import org.hamcrest.CoreMatchers; -import org.junit.Assert; -import org.junit.Test; -import org.opendaylight.controller.config.api.LookupRegistry; -import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry; -import org.opendaylight.yangtools.yang.common.QName; - -public class NetconfOperationServiceImplTest { - - private static final Date date1970_01_01; - - static { - try { - date1970_01_01 = new SimpleDateFormat("yyyy-MM-dd").parse("1970-01-01"); - } catch (ParseException e) { - throw new IllegalStateException(e); - } - } - - @Test - public void testCheckConsistencyBetweenYangStoreAndConfig_ok() throws Exception { - NetconfOperationServiceImpl.checkConsistencyBetweenYangStoreAndConfig( - mockJmxClient("qname1", "qname2"), - mockYangStoreSnapshot("qname2", "qname1")); - } - - @Test - public void testCheckConsistencyBetweenYangStoreAndConfig_ok2() throws Exception { - NetconfOperationServiceImpl.checkConsistencyBetweenYangStoreAndConfig( - mockJmxClient("qname1", "qname2", "qname4", "qname5"), - mockYangStoreSnapshot("qname2", "qname1")); - } - - @Test - public void testCheckConsistencyBetweenYangStoreAndConfig_ok3() throws Exception { - NetconfOperationServiceImpl.checkConsistencyBetweenYangStoreAndConfig( - mockJmxClient(), - mockYangStoreSnapshot()); - } - - @Test - public void testCheckConsistencyBetweenYangStoreAndConfig_yangStoreMore() throws Exception { - try { - NetconfOperationServiceImpl.checkConsistencyBetweenYangStoreAndConfig(mockJmxClient("qname1"), - mockYangStoreSnapshot("qname2", "qname1")); - fail("An exception of type " + IllegalStateException.class + " was expected"); - } catch (IllegalStateException e) { - String message = e.getMessage(); - Assert.assertThat( - message, - CoreMatchers - .containsString("missing from config subsystem but present in yangstore: [(namespace?revision=1970-01-01)qname2]")); - Assert.assertThat( - message, - CoreMatchers - .containsString("All modules present in config: [(namespace?revision=1970-01-01)qname1]")); - } - } - - private YangStoreSnapshot mockYangStoreSnapshot(final String... qnames) { - YangStoreSnapshot mock = mock(YangStoreSnapshot.class); - - Map> map = Maps.newHashMap(); - - Map innerMap = Maps.newHashMap(); - - int i = 1; - for (String qname : qnames) { - innerMap.put(Integer.toString(i++), mockMBeanEntry(qname)); - } - - map.put("1", innerMap); - - doReturn(map).when(mock).getModuleMXBeanEntryMap(); - - return mock; - } - - private ModuleMXBeanEntry mockMBeanEntry(final String qname) { - ModuleMXBeanEntry mock = mock(ModuleMXBeanEntry.class); - QName q = getQName(qname); - doReturn(q).when(mock).getYangModuleQName(); - return mock; - } - - private QName getQName(final String qname) { - return QName.create(URI.create("namespace"), date1970_01_01, qname); - } - - private LookupRegistry mockJmxClient(final String... visibleQNames) { - LookupRegistry mock = mock(LookupRegistry.class); - Set qnames = Sets.newHashSet(); - for (String visibleQName : visibleQNames) { - QName q = getQName(visibleQName); - qnames.add(q.toString()); - } - doReturn(qnames).when(mock).getAvailableModuleFactoryQNames(); - return mock; - } -} diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPusherImpl.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPusherImpl.java index cf9958e7f8..22b061a128 100644 --- a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPusherImpl.java +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPusherImpl.java @@ -33,10 +33,10 @@ import org.opendaylight.controller.config.api.ConflictingVersionException; import org.opendaylight.controller.config.persist.api.ConfigPusher; import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; import org.opendaylight.controller.config.persist.api.Persister; +import org.opendaylight.controller.netconf.api.Capability; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; -import org.opendaylight.controller.netconf.mapping.api.Capability; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; @@ -115,7 +115,7 @@ public class ConfigPusherImpl implements ConfigPusher { */ private synchronized EditAndCommitResponse pushConfigWithConflictingVersionRetries(ConfigSnapshotHolder configSnapshotHolder) throws NetconfDocumentedException { ConflictingVersionException lastException; - Stopwatch stopwatch = new Stopwatch(); + Stopwatch stopwatch = Stopwatch.createUnstarted(); do { String idForReporting = configSnapshotHolder.toString(); SortedSet expectedCapabilities = checkNotNull(configSnapshotHolder.getCapabilities(), @@ -137,7 +137,7 @@ public class ConfigPusherImpl implements ConfigPusher { } private NetconfOperationService getOperationServiceWithRetries(Set expectedCapabilities, String idForReporting) { - Stopwatch stopwatch = new Stopwatch().start(); + Stopwatch stopwatch = Stopwatch.createStarted(); NotEnoughCapabilitiesException lastException; do { try { @@ -177,20 +177,20 @@ public class ConfigPusherImpl implements ConfigPusher { } catch(RuntimeException e) { throw new NotEnoughCapabilitiesException("Netconf service not stable for " + idForReporting, e); } - Set notFoundDiff = computeNotFoundCapabilities(expectedCapabilities, serviceCandidate); + Set notFoundDiff = computeNotFoundCapabilities(expectedCapabilities, configNetconfConnector); if (notFoundDiff.isEmpty()) { return serviceCandidate; } else { serviceCandidate.close(); LOG.trace("Netconf server did not provide required capabilities for {} ", idForReporting, "Expected but not found: {}, all expected {}, current {}", - notFoundDiff, expectedCapabilities, serviceCandidate.getCapabilities() + notFoundDiff, expectedCapabilities, configNetconfConnector.getCapabilities() ); throw new NotEnoughCapabilitiesException("Not enough capabilities for " + idForReporting + ". Expected but not found: " + notFoundDiff); } } - private static Set computeNotFoundCapabilities(Set expectedCapabilities, NetconfOperationService serviceCandidate) { + private static Set computeNotFoundCapabilities(Set expectedCapabilities, NetconfOperationServiceFactory serviceCandidate) { Collection actual = Collections2.transform(serviceCandidate.getCapabilities(), new Function() { @Override public String apply(@Nonnull final Capability input) { @@ -230,7 +230,7 @@ public class ConfigPusherImpl implements ConfigPusher { throw new IllegalStateException("Cannot parse " + configSnapshotHolder); } LOG.trace("Pushing last configuration to netconf: {}", configSnapshotHolder); - Stopwatch stopwatch = new Stopwatch().start(); + Stopwatch stopwatch = Stopwatch.createStarted(); NetconfMessage editConfigMessage = createEditConfigMessage(xmlToBePersisted); Document editResponseMessage = sendRequestGetResponseCheckIsOK(editConfigMessage, operationService, diff --git a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterActivator.java b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterActivator.java index 787f8b10b0..b27bec3c83 100644 --- a/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterActivator.java +++ b/opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterActivator.java @@ -13,10 +13,10 @@ import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import javax.management.MBeanServer; import org.opendaylight.controller.config.persist.api.ConfigPusher; import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; import org.opendaylight.controller.netconf.persist.impl.ConfigPusherImpl; import org.opendaylight.controller.netconf.persist.impl.PersisterAggregator; @@ -70,7 +70,7 @@ public class ConfigPersisterActivator implements BundleActivator { InnerCustomizer innerCustomizer = new InnerCustomizer(configs, maxWaitForCapabilitiesMillis, conflictingVersionTimeoutMillis, persisterAggregator); OuterCustomizer outerCustomizer = new OuterCustomizer(context, innerCustomizer); - new ServiceTracker<>(context, NetconfOperationProvider.class, outerCustomizer).open(); + new ServiceTracker<>(context, NetconfOperationServiceFactory.class, outerCustomizer).open(); } private long getConflictingVersionTimeoutMillis(PropertiesProviderBaseImpl propertiesProvider) { @@ -103,7 +103,7 @@ public class ConfigPersisterActivator implements BundleActivator { ")"; } - class OuterCustomizer implements ServiceTrackerCustomizer { + class OuterCustomizer implements ServiceTrackerCustomizer { private final BundleContext context; private final InnerCustomizer innerCustomizer; @@ -113,7 +113,7 @@ public class ConfigPersisterActivator implements BundleActivator { } @Override - public NetconfOperationProvider addingService(ServiceReference reference) { + public NetconfOperationServiceFactory addingService(ServiceReference reference) { LOG.trace("Got OuterCustomizer.addingService {}", reference); // JMX was registered, track config-netconf-connector Filter filter; @@ -127,12 +127,12 @@ public class ConfigPersisterActivator implements BundleActivator { } @Override - public void modifiedService(ServiceReference reference, NetconfOperationProvider service) { + public void modifiedService(ServiceReference reference, NetconfOperationServiceFactory service) { } @Override - public void removedService(ServiceReference reference, NetconfOperationProvider service) { + public void removedService(ServiceReference reference, NetconfOperationServiceFactory service) { } } @@ -141,7 +141,9 @@ public class ConfigPersisterActivator implements BundleActivator { private final List configs; private final PersisterAggregator persisterAggregator; private final long maxWaitForCapabilitiesMillis, conflictingVersionTimeoutMillis; - + // This inner customizer has its filter to find the right operation service, but it gets triggered after any + // operation service appears. This means that it could start pushing thread up to N times (N = number of operation services spawned in OSGi) + private final AtomicBoolean alreadyStarted = new AtomicBoolean(false); InnerCustomizer(List configs, long maxWaitForCapabilitiesMillis, long conflictingVersionTimeoutMillis, PersisterAggregator persisterAggregator) { @@ -153,6 +155,10 @@ public class ConfigPersisterActivator implements BundleActivator { @Override public NetconfOperationServiceFactory addingService(ServiceReference reference) { + if(alreadyStarted.compareAndSet(false, true) == false) { + //Prevents multiple calls to this method spawning multiple pushing threads + return reference.getBundle().getBundleContext().getService(reference); + } LOG.trace("Got InnerCustomizer.addingService {}", reference); NetconfOperationServiceFactory service = reference.getBundle().getBundleContext().getService(reference); @@ -196,10 +202,12 @@ public class ConfigPersisterActivator implements BundleActivator { @Override public void modifiedService(ServiceReference reference, NetconfOperationServiceFactory service) { + LOG.trace("Got InnerCustomizer.modifiedService {}", reference); } @Override public void removedService(ServiceReference reference, NetconfOperationServiceFactory service) { + LOG.trace("Got InnerCustomizer.removedService {}", reference); } } diff --git a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterTest.java b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterTest.java index 142d8f5226..7b79e41f38 100644 --- a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterTest.java +++ b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/ConfigPersisterTest.java @@ -20,8 +20,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.config.api.ConflictingVersionException; +import org.opendaylight.controller.netconf.api.Capability; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.netconf.mapping.api.Capability; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; @@ -41,9 +41,10 @@ public class ConfigPersisterTest { private TestingExceptionHandler handler; - private void setUpContextAndStartPersister(String requiredCapability) throws Exception { + private void setUpContextAndStartPersister(String requiredCapability, final NetconfOperationService conflictingService) throws Exception { DummyAdapterWithInitialSnapshot.expectedCapability = requiredCapability; ctx = new MockedBundleContext(1000, 1000); + doReturn(getConflictingService()).when(ctx.serviceFactory).createService(anyString()); configPersisterActivator = new ConfigPersisterActivator(); configPersisterActivator.start(ctx.getBundleContext()); } @@ -62,7 +63,7 @@ public class ConfigPersisterTest { @Test public void testPersisterNotAllCapabilitiesProvided() throws Exception { - setUpContextAndStartPersister("required-cap"); + setUpContextAndStartPersister("required-cap", getConflictingService()); Thread.sleep(2000); handler.assertException(IllegalStateException.class, "Max wait for capabilities reached.Not enough capabilities " + "for . Expected but not found: [required-cap]"); @@ -71,7 +72,7 @@ public class ConfigPersisterTest { @Test public void testPersisterSuccessfulPush() throws Exception { - setUpContextAndStartPersister("cap1"); + setUpContextAndStartPersister("cap1", getConflictingService()); NetconfOperationService service = getWorkingService(getOKDocument()); doReturn(service).when(ctx.serviceFactory).createService(anyString()); Thread.sleep(2000); @@ -86,7 +87,7 @@ public class ConfigPersisterTest { public NetconfOperationService getWorkingService(Document document) throws SAXException, IOException, NetconfDocumentedException { NetconfOperationService service = mock(NetconfOperationService.class); Capability capability = mock(Capability.class); - doReturn(Sets.newHashSet(capability)).when(service).getCapabilities(); +// doReturn(Sets.newHashSet(capability)).when(service).getCapabilities(); doReturn("cap1").when(capability).getCapabilityUri(); @@ -109,9 +110,8 @@ public class ConfigPersisterTest { @Test public void testPersisterConflictingVersionException() throws Exception { - setUpContextAndStartPersister("cap1"); + setUpContextAndStartPersister("cap1", getConflictingService()); - doReturn(getConflictingService()).when(ctx.serviceFactory).createService(anyString()); Thread.sleep(2000); handler.assertException(IllegalStateException.class, "Max wait for conflicting version stabilization timeout"); } @@ -131,7 +131,7 @@ public class ConfigPersisterTest { @Test public void testSuccessConflictingVersionException() throws Exception { - setUpContextAndStartPersister("cap1"); + setUpContextAndStartPersister("cap1", getConflictingService()); doReturn(getConflictingService()).when(ctx.serviceFactory).createService(anyString()); Thread.sleep(500); // working service: diff --git a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/MockedBundleContext.java b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/MockedBundleContext.java index 0d866ecda7..bd18c8c30e 100644 --- a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/MockedBundleContext.java +++ b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/osgi/MockedBundleContext.java @@ -12,6 +12,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -28,7 +29,7 @@ import org.opendaylight.controller.config.persist.api.ConfigPusher; import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder; import org.opendaylight.controller.config.persist.api.Persister; import org.opendaylight.controller.config.persist.api.PropertiesProvider; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; +import org.opendaylight.controller.netconf.api.Capability; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; import org.opendaylight.controller.netconf.persist.impl.DummyAdapter; @@ -60,11 +61,11 @@ final class MockedBundleContext { doReturn(null).when(context).getProperty(anyString()); initContext(maxWaitForCapabilitiesMillis, conflictingVersionTimeoutMillis); - String outerFilterString = "(objectClass=org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider)"; + String outerFilterString = "(objectClass=org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory)"; doReturn(outerFilter).when(context).createFilter(outerFilterString); doNothing().when(context).addServiceListener(any(ServiceListener.class), eq(outerFilterString)); ServiceReference[] toBeReturned = {serviceReference}; - doReturn(toBeReturned).when(context).getServiceReferences(NetconfOperationProvider.class.getName(), null); + doReturn(toBeReturned).when(context).getServiceReferences(NetconfOperationServiceFactory.class.getName(), null); String innerFilterString = "innerfilter"; doReturn(innerFilterString).when(outerFilter).toString(); @@ -77,9 +78,12 @@ final class MockedBundleContext { doReturn(bundle).when(serviceReference).getBundle(); doReturn(context).when(bundle).getBundleContext(); doReturn("").when(serviceReference).toString(); + doReturn("context").when(context).toString(); doReturn(serviceFactory).when(context).getService(any(ServiceReference.class)); doReturn(service).when(serviceFactory).createService(anyString()); - doReturn(Collections.emptySet()).when(service).getCapabilities(); + final Capability cap = mock(Capability.class); + doReturn("cap1").when(cap).getCapabilityUri(); + doReturn(Collections.singleton(cap)).when(serviceFactory).getCapabilities(); doNothing().when(service).close(); doReturn("serviceFactoryMock").when(serviceFactory).toString(); diff --git a/opendaylight/netconf/ietf-netconf-monitoring/src/main/yang/ietf-netconf-monitoring.yang b/opendaylight/netconf/ietf-netconf-monitoring/src/main/yang/ietf-netconf-monitoring.yang index b4ad89b558..84756fc713 100644 --- a/opendaylight/netconf/ietf-netconf-monitoring/src/main/yang/ietf-netconf-monitoring.yang +++ b/opendaylight/netconf/ietf-netconf-monitoring/src/main/yang/ietf-netconf-monitoring.yang @@ -9,7 +9,9 @@ module ietf-netconf-monitoring { import ietf-yang-types { prefix yang; + revision-date "2010-09-24"; } + import ietf-inet-types { prefix inet; } diff --git a/opendaylight/netconf/ietf-netconf-notifications/pom.xml b/opendaylight/netconf/ietf-netconf-notifications/pom.xml new file mode 100644 index 0000000000..1ce3b031b7 --- /dev/null +++ b/opendaylight/netconf/ietf-netconf-notifications/pom.xml @@ -0,0 +1,63 @@ + + + + + 4.0.0 + + org.opendaylight.controller + netconf-subsystem + 0.3.0-SNAPSHOT + + ietf-netconf-notifications + bundle + ${project.artifactId} + + + + com.google.guava + guava + + + org.opendaylight.controller + ietf-netconf + + + org.opendaylight.yangtools.model + ietf-yang-types + + + org.slf4j + slf4j-api + + + + + + + + org.apache.felix + maven-bundle-plugin + + + + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.*, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.*, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.* + + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + + + diff --git a/opendaylight/netconf/ietf-netconf-notifications/src/main/yang/ietf-netconf-notifications@2012-02-06.yang b/opendaylight/netconf/ietf-netconf-notifications/src/main/yang/ietf-netconf-notifications@2012-02-06.yang new file mode 100644 index 0000000000..a04799dcf6 --- /dev/null +++ b/opendaylight/netconf/ietf-netconf-notifications/src/main/yang/ietf-netconf-notifications@2012-02-06.yang @@ -0,0 +1,363 @@ +module ietf-netconf-notifications { + + namespace + "urn:ietf:params:xml:ns:yang:ietf-netconf-notifications"; + + prefix ncn; + + import ietf-inet-types { prefix inet; } + import ietf-netconf { prefix nc; } + + organization + "IETF NETCONF (Network Configuration Protocol) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: Bert Wijnen + + + WG Chair: Mehmet Ersue + + + Editor: Andy Bierman + "; + + description + "This module defines a YANG data model for use with the + NETCONF protocol that allows the NETCONF client to + receive common NETCONF base event notifications. + + Copyright (c) 2012 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 6470; see + the RFC itself for full legal notices."; + + revision "2012-02-06" { + description + "Initial version. Errata 3957 added."; + reference + "RFC 6470: NETCONF Base Notifications"; + } + + grouping common-session-parms { + description + "Common session parameters to identify a + management session."; + + leaf username { + type string; + mandatory true; + description + "Name of the user for the session."; + } + + leaf session-id { + type nc:session-id-or-zero-type; + mandatory true; + description + "Identifier of the session. + A NETCONF session MUST be identified by a non-zero value. + A non-NETCONF session MAY be identified by the value zero."; + } + + leaf source-host { + type inet:ip-address; + description + "Address of the remote host for the session."; + } + } + + + + + + + + + grouping changed-by-parms { + description + "Common parameters to identify the source + of a change event, such as a configuration + or capability change."; + + container changed-by { + description + "Indicates the source of the change. + If caused by internal action, then the + empty leaf 'server' will be present. + If caused by a management session, then + the name, remote host address, and session ID + of the session that made the change will be reported."; + choice server-or-user { + mandatory true; + leaf server { + type empty; + description + "If present, the change was caused + by the server."; + } + + case by-user { + uses common-session-parms; + } + } // choice server-or-user + } // container changed-by-parms + } + + + notification netconf-config-change { + description + "Generated when the NETCONF server detects that the + or configuration datastore + has been changed by a management session. + The notification summarizes the edits that + have been detected. + + The server MAY choose to also generate this + notification while loading a datastore during the + boot process for the device."; + + uses changed-by-parms; + + + + + + leaf datastore { + type enumeration { + enum running { + description "The datastore has changed."; + } + enum startup { + description "The datastore has changed"; + } + } + default "running"; + description + "Indicates which configuration datastore has changed."; + } + + list edit { + description + "An edit record SHOULD be present for each distinct + edit operation that the server has detected on + the target datastore. This list MAY be omitted + if the detailed edit operations are not known. + The server MAY report entries in this list for + changes not made by a NETCONF session (e.g., CLI)."; + + leaf target { + type instance-identifier; + description + "Topmost node associated with the configuration change. + A server SHOULD set this object to the node within + the datastore that is being altered. A server MAY + set this object to one of the ancestors of the actual + node that was changed, or omit this object, if the + exact node is not known."; + } + + leaf operation { + type nc:edit-operation-type; + description + "Type of edit operation performed. + A server MUST set this object to the NETCONF edit + operation performed on the target datastore."; + } + } // list edit + } // notification netconf-config-change + + + + + + + notification netconf-capability-change { + description + "Generated when the NETCONF server detects that + the server capabilities have changed. + Indicates which capabilities have been added, deleted, + and/or modified. The manner in which a server + capability is changed is outside the scope of this + document."; + + uses changed-by-parms; + + leaf-list added-capability { + type inet:uri; + description + "List of capabilities that have just been added."; + } + + leaf-list deleted-capability { + type inet:uri; + description + "List of capabilities that have just been deleted."; + } + + leaf-list modified-capability { + type inet:uri; + description + "List of capabilities that have just been modified. + A capability is considered to be modified if the + base URI for the capability has not changed, but + one or more of the parameters encoded at the end of + the capability URI have changed. + The new modified value of the complete URI is returned."; + } + } // notification netconf-capability-change + + + notification netconf-session-start { + description + "Generated when a NETCONF server detects that a + NETCONF session has started. A server MAY generate + this event for non-NETCONF management sessions. + Indicates the identity of the user that started + the session."; + uses common-session-parms; + } // notification netconf-session-start + + + + + notification netconf-session-end { + description + "Generated when a NETCONF server detects that a + NETCONF session has terminated. + A server MAY optionally generate this event for + non-NETCONF management sessions. Indicates the + identity of the user that owned the session, + and why the session was terminated."; + + uses common-session-parms; + + leaf killed-by { + when "../termination-reason = 'killed'"; + type nc:session-id-type; + description + "The ID of the session that directly caused this session + to be abnormally terminated. If this session was abnormally + terminated by a non-NETCONF session unknown to the server, + then this leaf will not be present."; + } + + leaf termination-reason { + type enumeration { + enum "closed" { + description + "The session was terminated by the client in normal + fashion, e.g., by the NETCONF + protocol operation."; + } + enum "killed" { + description + "The session was terminated in abnormal + fashion, e.g., by the NETCONF + protocol operation."; + } + enum "dropped" { + description + "The session was terminated because the transport layer + connection was unexpectedly closed."; + } + enum "timeout" { + description + "The session was terminated because of inactivity, + e.g., waiting for the message or + messages."; + } + + + + enum "bad-hello" { + description + "The client's message was invalid."; + } + enum "other" { + description + "The session was terminated for some other reason."; + } + } + mandatory true; + description + "Reason the session was terminated."; + } + } // notification netconf-session-end + + + notification netconf-confirmed-commit { + description + "Generated when a NETCONF server detects that a + confirmed-commit event has occurred. Indicates the event + and the current state of the confirmed-commit procedure + in progress."; + reference + "RFC 6241, Section 8.4"; + + uses common-session-parms { + when "confirm-event != 'timeout'"; + } + + leaf confirm-event { + type enumeration { + enum "start" { + description + "The confirmed-commit procedure has started."; + } + enum "cancel" { + description + "The confirmed-commit procedure has been canceled, + e.g., due to the session being terminated, or an + explicit operation."; + } + enum "timeout" { + description + "The confirmed-commit procedure has been canceled + due to the confirm-timeout interval expiring. + The common session parameters will not be present + in this sub-mode."; + } + + enum "extend" { + description + "The confirmed-commit timeout has been extended, + e.g., by a new operation."; + } + enum "complete" { + description + "The confirmed-commit procedure has been completed."; + } + } + mandatory true; + description + "Indicates the event that caused the notification."; + } + + leaf timeout { + when + "../confirm-event = 'start' or ../confirm-event = 'extend'"; + type uint32; + units "seconds"; + description + "The configured timeout value if the event type + is 'start' or 'extend'. This value represents + the approximate number of seconds from the event + time when the 'timeout' event might occur."; + } + } // notification netconf-confirmed-commit + +} diff --git a/opendaylight/netconf/ietf-netconf-notifications/src/main/yang/nc-notifications@2008-07-14.yang b/opendaylight/netconf/ietf-netconf-notifications/src/main/yang/nc-notifications@2008-07-14.yang new file mode 100644 index 0000000000..fb9aac133b --- /dev/null +++ b/opendaylight/netconf/ietf-netconf-notifications/src/main/yang/nc-notifications@2008-07-14.yang @@ -0,0 +1,95 @@ +module nc-notifications { + + namespace "urn:ietf:params:xml:ns:netmod:notification"; + prefix "manageEvent"; + + import ietf-yang-types{ prefix yang; } + import notifications { prefix ncEvent; } + + organization + "IETF NETCONF WG"; + + contact + "netconf@ietf.org"; + + description + "Conversion of the 'manageEvent' XSD in the NETCONF + Notifications RFC."; + + reference + "RFC 5277"; + + revision 2008-07-14 { + description "RFC 5277 version."; + } + + container netconf { + description "Top-level element in the notification namespace"; + + config false; + + container streams { + description + "The list of event streams supported by the system. When + a query is issued, the returned set of streams is + determined based on user privileges."; + + list stream { + description + "Stream name, description and other information."; + key name; + min-elements 1; + + leaf name { + description + "The name of the event stream. If this is the default + NETCONF stream, this must have the value 'NETCONF'."; + type ncEvent:streamNameType; + } + + leaf description { + description + "A description of the event stream, including such + information as the type of events that are sent over + this stream."; + type string; + mandatory true; + } + + leaf replaySupport { + description + "A description of the event stream, including such + information as the type of events that are sent over + this stream."; + type boolean; + mandatory true; + } + + leaf replayLogCreationTime { + description + "The timestamp of the creation of the log used to support + the replay function on this stream. Note that this might + be earlier then the earliest available notification in + the log. This object is updated if the log resets for + some reason. This object MUST be present if replay is + supported."; + type yang:date-and-time; // xsd:dateTime is wrong! + } + } + } + } + + notification replayComplete { + description + "This notification is sent to signal the end of a replay + portion of a subscription."; + } + + notification notificationComplete { + description + "This notification is sent to signal the end of a notification + subscription. It is sent in the case that stopTime was + specified during the creation of the subscription.."; + } + +} diff --git a/opendaylight/netconf/ietf-netconf-notifications/src/main/yang/notifications@2008-07-14.yang b/opendaylight/netconf/ietf-netconf-notifications/src/main/yang/notifications@2008-07-14.yang new file mode 100644 index 0000000000..f107c2a8aa --- /dev/null +++ b/opendaylight/netconf/ietf-netconf-notifications/src/main/yang/notifications@2008-07-14.yang @@ -0,0 +1,83 @@ +module notifications { + + namespace "urn:ietf:params:xml:ns:netconf:notification:1.0"; + prefix "ncEvent"; + + import ietf-yang-types { prefix yang; } + + organization + "IETF NETCONF WG"; + + contact + "netconf@ops.ietf.org"; + + description + "Conversion of the 'ncEvent' XSD in the + NETCONF Notifications RFC."; + + reference + "RFC 5277."; + + revision 2008-07-14 { + description "RFC 5277 version."; + } + + typedef streamNameType { + description + "The name of an event stream."; + type string; + } + + rpc create-subscription { + description + "The command to create a notification subscription. It + takes as argument the name of the notification stream + and filter. Both of those options limit the content of + the subscription. In addition, there are two time-related + parameters, startTime and stopTime, which can be used to + select the time interval of interest to the notification + replay feature."; + + input { + leaf stream { + description + "An optional parameter that indicates which stream of events + is of interest. If not present, then events in the default + NETCONF stream will be sent."; + type streamNameType; + default "NETCONF"; + } + + anyxml filter { + description + "An optional parameter that indicates which subset of all + possible events is of interest. The format of this + parameter is the same as that of the filter parameter + in the NETCONF protocol operations. If not present, + all events not precluded by other parameters will + be sent."; + } + + leaf startTime { + description + "A parameter used to trigger the replay feature and + indicates that the replay should start at the time + specified. If start time is not present, this is not a + replay subscription."; + type yang:date-and-time; + } + + leaf stopTime { + // must ". >= ../startTime"; + description + "An optional parameter used with the optional replay + feature to indicate the newest notifications of + interest. If stop time is not present, the notifications + will continue until the subscription is terminated. + Must be used with startTime."; + type yang:date-and-time; + } + } + } +} + diff --git a/opendaylight/netconf/ietf-netconf/pom.xml b/opendaylight/netconf/ietf-netconf/pom.xml new file mode 100644 index 0000000000..6ed7a5f130 --- /dev/null +++ b/opendaylight/netconf/ietf-netconf/pom.xml @@ -0,0 +1,56 @@ + + + + + 4.0.0 + + org.opendaylight.controller + netconf-subsystem + 0.3.0-SNAPSHOT + + ietf-netconf + bundle + ${project.artifactId} + + + + + com.google.guava + guava + + + org.opendaylight.yangtools.model + ietf-inet-types + + + org.slf4j + slf4j-api + + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.* + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + + + diff --git a/opendaylight/netconf/ietf-netconf/src/main/yang/ietf-netconf@2011-06-01.yang b/opendaylight/netconf/ietf-netconf/src/main/yang/ietf-netconf@2011-06-01.yang new file mode 100644 index 0000000000..4bbb1c2792 --- /dev/null +++ b/opendaylight/netconf/ietf-netconf/src/main/yang/ietf-netconf@2011-06-01.yang @@ -0,0 +1,928 @@ +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/mdsal-netconf-connector/pom.xml b/opendaylight/netconf/mdsal-netconf-connector/pom.xml new file mode 100644 index 0000000000..2808672ca2 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/pom.xml @@ -0,0 +1,117 @@ + + + 4.0.0 + + + org.opendaylight.controller + netconf-subsystem + 0.3.0-SNAPSHOT + + mdsal-netconf-connector + bundle + ${project.artifactId} + + + + ${project.groupId} + netconf-api + + + ${project.groupId} + netconf-mapping-api + + + ${project.groupId} + netconf-util + + + com.google.guava + guava + + + org.opendaylight.yangtools + yang-data-impl + + + org.opendaylight.controller + sal-core-api + + + org.opendaylight.controller + commons.logback_settings + + + org.opendaylight.yangtools + mockito-configuration + + + org.slf4j + slf4j-api + + + org.opendaylight.controller + sal-core-api + + + org.opendaylight.controller + config-util + + + org.opendaylight.yangtools + yang-data-operations + 0.7.0-SNAPSHOT + + + + + + + + org.apache.felix + maven-bundle-plugin + + + * + + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + config + + generate-sources + + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + ${jmxGeneratorPath} + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + ${salGeneratorPath} + + + true + + + + + + org.opendaylight.controller + yang-jmx-generator-plugin + ${config.version} + + + + + + + diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/mapper/NetconfMdsalMapperModule.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/mapper/NetconfMdsalMapperModule.java new file mode 100644 index 0000000000..bf8bdb06f3 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/mapper/NetconfMdsalMapperModule.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.config.yang.netconf.mdsal.mapper; + +import org.opendaylight.controller.netconf.mdsal.connector.MdsalNetconfOperationServiceFactory; + +public class NetconfMdsalMapperModule extends org.opendaylight.controller.config.yang.netconf.mdsal.mapper.AbstractNetconfMdsalMapperModule { + public NetconfMdsalMapperModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { + super(identifier, dependencyResolver); + } + + public NetconfMdsalMapperModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.controller.config.yang.netconf.mdsal.mapper.NetconfMdsalMapperModule oldModule, java.lang.AutoCloseable oldInstance) { + super(identifier, dependencyResolver, oldModule, oldInstance); + } + + @Override + public void customValidation() { + // add custom validation form module attributes here. + } + + @Override + public java.lang.AutoCloseable createInstance() { + final MdsalNetconfOperationServiceFactory mdsalNetconfOperationServiceFactory = new MdsalNetconfOperationServiceFactory(getRootSchemaServiceDependency(), getDomBrokerDependency()) { + @Override + public void close() throws Exception { + super.close(); + getMapperAggregatorDependency().onRemoveNetconfOperationServiceFactory(this); + } + }; + getMapperAggregatorDependency().onAddNetconfOperationServiceFactory(mdsalNetconfOperationServiceFactory); + return mdsalNetconfOperationServiceFactory; + } + +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/mapper/NetconfMdsalMapperModuleFactory.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/mapper/NetconfMdsalMapperModuleFactory.java new file mode 100644 index 0000000000..4eb0563ad4 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/mapper/NetconfMdsalMapperModuleFactory.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +/* +* Generated file +* +* Generated from: yang module name: netconf-mdsal-mapper yang module local name: netconf-mdsal-mapper +* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator +* Generated at: Wed Jan 14 14:58:42 CET 2015 +* +* Do not modify this file unless it is present under src/main directory +*/ +package org.opendaylight.controller.config.yang.netconf.mdsal.mapper; +public class NetconfMdsalMapperModuleFactory extends org.opendaylight.controller.config.yang.netconf.mdsal.mapper.AbstractNetconfMdsalMapperModuleFactory { + +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/CurrentSchemaContext.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/CurrentSchemaContext.java new file mode 100644 index 0000000000..1aa38eb80c --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/CurrentSchemaContext.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.sal.core.api.model.SchemaService; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; + +public class CurrentSchemaContext implements SchemaContextListener, AutoCloseable { + final AtomicReference currentContext = new AtomicReference(); + private final ListenerRegistration schemaContextListenerListenerRegistration; + private final Set listeners = Collections.synchronizedSet(Sets.newHashSet()); + + public SchemaContext getCurrentContext() { + Preconditions.checkState(currentContext.get() != null, "Current context not received"); + return currentContext.get(); + } + + public CurrentSchemaContext(final SchemaService schemaService) { + schemaContextListenerListenerRegistration = schemaService.registerSchemaContextListener(this); + } + + @Override + public void onGlobalContextUpdated(final SchemaContext schemaContext) { + currentContext.set(schemaContext); + // FIXME is notifying all the listeners from this callback wise ? + final Set addedCaps = MdsalNetconfOperationServiceFactory.transformCapabilities(currentContext.get()); + for (final CapabilityListener listener : listeners) { + listener.onCapabilitiesAdded(addedCaps); + } + } + + @Override + public void close() throws Exception { + listeners.clear(); + schemaContextListenerListenerRegistration.close(); + currentContext.set(null); + } + + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + listener.onCapabilitiesAdded(MdsalNetconfOperationServiceFactory.transformCapabilities(currentContext.get())); + listeners.add(listener); + return new AutoCloseable() { + @Override + public void close() throws Exception { + listeners.remove(listener); + } + }; + } +} \ No newline at end of file diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/MdsalNetconfOperationService.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/MdsalNetconfOperationService.java new file mode 100644 index 0000000000..cc22dd51aa --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/MdsalNetconfOperationService.java @@ -0,0 +1,35 @@ +/* + * 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.mdsal.connector; + +import java.util.Set; +import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; + +public class MdsalNetconfOperationService implements NetconfOperationService { + + private final OperationProvider operationProvider; + + public MdsalNetconfOperationService(final CurrentSchemaContext schemaContext, final String netconfSessionIdForReporting, + final DOMDataBroker dataBroker) { + this.operationProvider = new OperationProvider(netconfSessionIdForReporting, schemaContext, dataBroker); + } + + @Override + public void close() { + + } + + @Override + public Set getNetconfOperations() { + return operationProvider.getOperations(); + } + +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/MdsalNetconfOperationServiceFactory.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/MdsalNetconfOperationServiceFactory.java new file mode 100644 index 0000000000..ebb0e9d3d1 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/MdsalNetconfOperationServiceFactory.java @@ -0,0 +1,163 @@ +/* + * 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.mdsal.connector; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.sal.core.api.model.SchemaService; +import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MdsalNetconfOperationServiceFactory implements NetconfOperationServiceFactory, AutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(MdsalNetconfOperationServiceFactory.class); + + private final DOMDataBroker dataBroker; + private final CurrentSchemaContext currentSchemaContext; + + public MdsalNetconfOperationServiceFactory(final SchemaService schemaService, final DOMDataBroker domDataBroker) { + this.currentSchemaContext = new CurrentSchemaContext(Preconditions.checkNotNull(schemaService)); + this.dataBroker = Preconditions.checkNotNull(domDataBroker); + } + + @Override + public MdsalNetconfOperationService createService(final String netconfSessionIdForReporting) { + return new MdsalNetconfOperationService(currentSchemaContext, netconfSessionIdForReporting, dataBroker); + } + + @Override + public void close() throws Exception { + currentSchemaContext.close(); + } + + @Override + public Set getCapabilities() { + return transformCapabilities(currentSchemaContext.getCurrentContext()); + } + + static Set transformCapabilities(final SchemaContext currentContext1) { + final Set capabilities = new HashSet<>(); + // [RFC6241] 8.3. Candidate Configuration Capability + capabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:candidate:1.0")); + + final SchemaContext currentContext = currentContext1; + final Set modules = currentContext.getModules(); + for (final Module module : modules) { + if(currentContext.getModuleSource(module).isPresent()) { + capabilities.add(new YangStoreCapability(module, currentContext.getModuleSource(module).get())); + } else { + LOG.warn("Missing source for module {}. This module will not be available from netconf server", + module); + } + } + + return capabilities; + } + + @Override + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + return currentSchemaContext.registerCapabilityListener(listener); + } + + private static class BasicCapability implements Capability { + + private final String capability; + + private BasicCapability(final String capability) { + this.capability = capability; + } + + @Override + public String getCapabilityUri() { + return capability; + } + + @Override + public Optional getModuleNamespace() { + return Optional.absent(); + } + + @Override + public Optional getModuleName() { + return Optional.absent(); + } + + @Override + public Optional getRevision() { + return Optional.absent(); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.absent(); + } + + @Override + public Collection getLocation() { + return Collections.emptyList(); + } + + @Override + public String toString() { + return capability; + } + } + + private static final class YangStoreCapability extends BasicCapability { + + private final String content; + private final String revision; + private final String moduleName; + private final String moduleNamespace; + + public YangStoreCapability(final Module module, final String moduleContent) { + super(toCapabilityURI(module)); + this.content = moduleContent; + this.moduleName = module.getName(); + this.moduleNamespace = module.getNamespace().toString(); + this.revision = SimpleDateFormatUtil.getRevisionFormat().format(module.getRevision()); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.of(content); + } + + private static String toCapabilityURI(final Module module) { + return String.valueOf(module.getNamespace()) + "?module=" + + module.getName() + "&revision=" + SimpleDateFormatUtil.getRevisionFormat().format(module.getRevision()); + } + + @Override + public Optional getModuleName() { + return Optional.of(moduleName); + } + + @Override + public Optional getModuleNamespace() { + return Optional.of(moduleNamespace); + } + + @Override + public Optional getRevision() { + return Optional.of(revision); + } + } +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/OperationProvider.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/OperationProvider.java new file mode 100644 index 0000000000..c881ae2e4e --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/OperationProvider.java @@ -0,0 +1,50 @@ +/* + * 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.mdsal.connector; + +import com.google.common.collect.Sets; +import java.util.Set; +import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +import org.opendaylight.controller.netconf.mdsal.connector.ops.Commit; +import org.opendaylight.controller.netconf.mdsal.connector.ops.DiscardChanges; +import org.opendaylight.controller.netconf.mdsal.connector.ops.EditConfig; +import org.opendaylight.controller.netconf.mdsal.connector.ops.Lock; +import org.opendaylight.controller.netconf.mdsal.connector.ops.Unlock; +import org.opendaylight.controller.netconf.mdsal.connector.ops.get.Get; +import org.opendaylight.controller.netconf.mdsal.connector.ops.get.GetConfig; + +final class OperationProvider { + + private final String netconfSessionIdForReporting; + private final CurrentSchemaContext schemaContext; + private final DOMDataBroker dataBroker; + private final TransactionProvider transactionProvider; + + public OperationProvider(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext, final DOMDataBroker dataBroker) { + this.netconfSessionIdForReporting = netconfSessionIdForReporting; + this.schemaContext = schemaContext; + this.dataBroker = dataBroker; + this.transactionProvider = new TransactionProvider(dataBroker, netconfSessionIdForReporting); + + } + + Set getOperations() { + return Sets.newHashSet( + new Commit(netconfSessionIdForReporting, transactionProvider), + new DiscardChanges(netconfSessionIdForReporting, transactionProvider), + new EditConfig(netconfSessionIdForReporting, schemaContext, transactionProvider), + new Get(netconfSessionIdForReporting, schemaContext, transactionProvider), + new GetConfig(netconfSessionIdForReporting, schemaContext, transactionProvider), + new Lock(netconfSessionIdForReporting), + new Unlock(netconfSessionIdForReporting) + ); + } + +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/TransactionProvider.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/TransactionProvider.java new file mode 100644 index 0000000000..f1b214b83e --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/TransactionProvider.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.CheckedFuture; +import java.util.ArrayList; +import java.util.List; +import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; +import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker; +import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//TODO make a global TransactionProvider for all Netconf sessions instead of each session having one. +public class TransactionProvider implements AutoCloseable{ + + private static final Logger LOG = LoggerFactory.getLogger(TransactionProvider.class); + + private final DOMDataBroker dataBroker; + + private DOMDataReadWriteTransaction candidateTransaction = null; + private DOMDataReadWriteTransaction runningTransaction = null; + private final List allOpenReadWriteTransactions = new ArrayList<>(); + + private final String netconfSessionIdForReporting; + + private static final String NO_TRANSACTION_FOUND_FOR_SESSION = "No candidateTransaction found for session "; + + + public TransactionProvider(DOMDataBroker dataBroker, String netconfSessionIdForReporting) { + this.dataBroker = dataBroker; + this.netconfSessionIdForReporting = netconfSessionIdForReporting; + } + + @Override + public synchronized void close() throws Exception { + for (DOMDataReadWriteTransaction rwt : allOpenReadWriteTransactions) { + rwt.cancel(); + } + + allOpenReadWriteTransactions.clear(); + } + + public synchronized Optional getCandidateTransaction() { + if (candidateTransaction == null) { + return Optional.absent(); + } + + return Optional.of(candidateTransaction); + } + + public synchronized DOMDataReadWriteTransaction getOrCreateTransaction() { + if (getCandidateTransaction().isPresent()) { + return getCandidateTransaction().get(); + } + + candidateTransaction = dataBroker.newReadWriteTransaction(); + allOpenReadWriteTransactions.add(candidateTransaction); + return candidateTransaction; + } + + public synchronized boolean commitTransaction() throws NetconfDocumentedException { + if (!getCandidateTransaction().isPresent()) { + throw new NetconfDocumentedException(NO_TRANSACTION_FOUND_FOR_SESSION + netconfSessionIdForReporting, + ErrorType.application, ErrorTag.operation_failed, ErrorSeverity.error); + } + + CheckedFuture future = candidateTransaction.submit(); + try { + future.checkedGet(); + } catch (TransactionCommitFailedException e) { + LOG.debug("Transaction {} failed on", candidateTransaction, e); + throw new NetconfDocumentedException("Transaction commit failed on " + e.getMessage() + " " + netconfSessionIdForReporting, + ErrorType.application, ErrorTag.operation_failed, ErrorSeverity.error); + } + allOpenReadWriteTransactions.remove(candidateTransaction); + candidateTransaction = null; + + return true; + } + + public synchronized void abortTransaction() { + LOG.debug("Aborting current candidateTransaction"); + Optional otx = getCandidateTransaction(); + Preconditions.checkState(otx.isPresent(), NO_TRANSACTION_FOUND_FOR_SESSION + netconfSessionIdForReporting); + candidateTransaction.cancel(); + allOpenReadWriteTransactions.remove(candidateTransaction); + candidateTransaction = null; + } + + public synchronized DOMDataReadWriteTransaction createRunningTransaction() { + runningTransaction = dataBroker.newReadWriteTransaction(); + allOpenReadWriteTransactions.add(runningTransaction); + return runningTransaction; + } + + public synchronized boolean commitRunningTransaction(DOMDataReadWriteTransaction tx) throws NetconfDocumentedException { + allOpenReadWriteTransactions.remove(tx); + + CheckedFuture future = tx.submit(); + try { + future.checkedGet(); + } catch (TransactionCommitFailedException e) { + LOG.debug("Transaction {} failed on", tx, e); + throw new NetconfDocumentedException("Transaction commit failed on " + e.getMessage() + " " + netconfSessionIdForReporting, + ErrorType.application, ErrorTag.operation_failed, ErrorSeverity.error); + } + + return true; + } + + public synchronized void abortRunningTransaction(DOMDataReadWriteTransaction tx) { + LOG.debug("Aborting current running Transaction"); + Preconditions.checkState(runningTransaction != null, NO_TRANSACTION_FOUND_FOR_SESSION + netconfSessionIdForReporting); + tx.cancel(); + allOpenReadWriteTransactions.remove(tx); + } + +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Commit.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Commit.java new file mode 100644 index 0000000000..15396cfbf8 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Commit.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector.ops; + +import com.google.common.base.Optional; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.mdsal.connector.TransactionProvider; +import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class Commit extends AbstractLastNetconfOperation{ + + private static final Logger LOG = LoggerFactory.getLogger(Commit.class); + + private static final String OPERATION_NAME = "commit"; + private final TransactionProvider transactionProvider; + + public Commit(final String netconfSessionIdForReporting, final TransactionProvider transactionProvider) { + super(netconfSessionIdForReporting); + this.transactionProvider = transactionProvider; + + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + + boolean commitStatus = transactionProvider.commitTransaction(); + LOG.trace("Transaction commited succesfuly", commitStatus); + + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + @Override + protected String getOperationName() { + return OPERATION_NAME; + } + +} diff --git a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/InitiateInstallSnapshot.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Datastore.java similarity index 51% rename from opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/InitiateInstallSnapshot.java rename to opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Datastore.java index 7844914873..0f86c5a1d1 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/base/messages/InitiateInstallSnapshot.java +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Datastore.java @@ -1,16 +1,13 @@ /* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.cluster.raft.base.messages; +package org.opendaylight.controller.netconf.mdsal.connector.ops; -/** - * Internal message by Leader to initiate an install snapshot - */ -public class InitiateInstallSnapshot { +public enum Datastore { + candidate, running } - diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/DiscardChanges.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/DiscardChanges.java new file mode 100644 index 0000000000..36f6d8e3cf --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/DiscardChanges.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector.ops; + +import com.google.common.base.Optional; +import java.util.HashMap; +import java.util.Map; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.mdsal.connector.TransactionProvider; +import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class DiscardChanges extends AbstractLastNetconfOperation{ + + private static final Logger LOG = LoggerFactory.getLogger(DiscardChanges.class); + + private static final String OPERATION_NAME = "discard-changes"; + + private final TransactionProvider transactionProvider; + + public DiscardChanges(final String netconfSessionIdForReporting, final TransactionProvider transactionProvider) { + super(netconfSessionIdForReporting); + this.transactionProvider = transactionProvider; + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + operationElement.getOnlyChildElement(OPERATION_NAME); + + try { + transactionProvider.abortTransaction(); + } catch (IllegalStateException e) { + LOG.warn("Abort failed ", e); + final Map errorInfo = new HashMap<>(); + errorInfo + .put(ErrorTag.operation_failed.name(), + "Operation failed. Use 'get-config' or 'edit-config' before triggering 'discard-changes' operation"); + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed, + ErrorSeverity.error, errorInfo); + } + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + @Override + protected String getOperationName() { + return OPERATION_NAME; + } +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/EditConfig.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/EditConfig.java new file mode 100644 index 0000000000..09be4163df --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/EditConfig.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector.ops; + +import com.google.common.base.Optional; +import com.google.common.util.concurrent.CheckedFuture; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.mdsal.connector.CurrentSchemaContext; +import org.opendaylight.controller.netconf.mdsal.connector.TransactionProvider; +import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils; +import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory; +import org.opendaylight.yangtools.yang.data.operations.DataModificationException; +import org.opendaylight.yangtools.yang.data.operations.DataModificationException.DataExistsException; +import org.opendaylight.yangtools.yang.data.operations.DataModificationException.DataMissingException; +import org.opendaylight.yangtools.yang.data.operations.DataOperations; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class EditConfig extends AbstractLastNetconfOperation { + + private static final Logger LOG = LoggerFactory.getLogger(EditConfig.class); + + private static final String OPERATION_NAME = "edit-config"; + private static final String CONFIG_KEY = "config"; + + private final CurrentSchemaContext schemaContext; + private final TransactionProvider transactionProvider; + + public EditConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext, final TransactionProvider transactionProvider) { + super(netconfSessionIdForReporting); + this.schemaContext = schemaContext; + this.transactionProvider = transactionProvider; + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + final XmlElement configElement = getConfigElement(operationElement); + + for (XmlElement element : configElement.getChildElements()) { + final String ns = element.getNamespace(); + final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element).get(); + YangInstanceIdentifier ident = YangInstanceIdentifier.of(schemaNode.getQName()); + + final NormalizedNode storedNode = readStoredNode(LogicalDatastoreType.CONFIGURATION, ident); + try { + final Optional> newNode = modifyNode(schemaNode, element, storedNode); + final DOMDataReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction(); + if (newNode.isPresent()) { + rwTx.put(LogicalDatastoreType.CONFIGURATION, ident, newNode.get()); + } else { + rwTx.delete(LogicalDatastoreType.CONFIGURATION, ident); + } + } catch (final DataModificationException e) { + if (e instanceof DataExistsException) { + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.protocol, ErrorTag.data_exists, ErrorSeverity.error); + } else if (e instanceof DataMissingException) { + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.protocol, ErrorTag.data_missing, ErrorSeverity.error); + } else { + //should never happen, since in edit-config only the 2 previous cases can happen + throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.protocol, ErrorTag.operation_failed, ErrorSeverity.error); + } + } + } + + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + private NormalizedNode readStoredNode(final LogicalDatastoreType logicalDatastoreType, final YangInstanceIdentifier path) throws NetconfDocumentedException{ + final DOMDataReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction(); + final CheckedFuture>, ReadFailedException> readFuture = rwTx.read(logicalDatastoreType, path); + try { + if (readFuture.checkedGet().isPresent()) { + final NormalizedNode node = readFuture.checkedGet().get(); + return node; + } else { + LOG.warn("Unable to read node : {} from {} datastore", path, logicalDatastoreType); + } + } catch (final ReadFailedException e) { + //only log this since DataOperations.modify will handle throwing an exception or writing the node. + LOG.warn("Unable to read stored data: {}", path, e); + } + + //we can return null here since DataOperations.modify handles null as input + return null; + } + + private Optional getSchemaNodeFromNamespace(final String namespace, final XmlElement element){ + Optional dataSchemaNode = Optional.absent(); + try { + //returns module with newest revision since findModuleByNamespace returns a set of modules and we only need the newest one + final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(new URI(namespace), null); + dataSchemaNode = Optional.of(module.getDataChildByName(element.getName())); + } catch (URISyntaxException e) { + LOG.debug("Unable to create URI for namespace : {}", namespace); + } + + return dataSchemaNode; + } + + private Optional> modifyNode(final DataSchemaNode schemaNode, final XmlElement element, final NormalizedNode storedNode) throws DataModificationException{ + if (schemaNode instanceof ContainerSchemaNode) { + final ContainerNode modifiedNode = + DomToNormalizedNodeParserFactory + .getInstance(DomUtils.defaultValueCodecProvider()) + .getContainerNodeParser() + .parse(Collections.singletonList(element.getDomElement()), (ContainerSchemaNode) schemaNode); + + final Optional oNode = DataOperations.modify((ContainerSchemaNode) schemaNode, (ContainerNode) storedNode, modifiedNode); + if (!oNode.isPresent()) { + return Optional.absent(); + } + + final NormalizedNode node = oNode.get(); + return Optional.>of(node); + } else if (schemaNode instanceof ListSchemaNode) { + final MapNode modifiedNode = + DomToNormalizedNodeParserFactory + .getInstance(DomUtils.defaultValueCodecProvider()) + .getMapNodeParser() + .parse(Collections.singletonList(element.getDomElement()), (ListSchemaNode) schemaNode); + + final Optional oNode = DataOperations.modify((ListSchemaNode) schemaNode, (MapNode) storedNode, modifiedNode); + if (!oNode.isPresent()) { + return Optional.absent(); + } + + final NormalizedNode node = oNode.get(); + return Optional.>of(node); + } else { + //this should never happen since edit-config on any other node type should not be possible nor makes sense + LOG.debug("DataNode from module is not ContainerSchemaNode nor ListSchemaNode, aborting.."); + return Optional.absent(); + } + + } + + private XmlElement getConfigElement(final XmlElement operationElement) throws NetconfDocumentedException{ + final Optional configChildNode = operationElement.getOnlyChildElementOptionally(CONFIG_KEY); + if (!configChildNode.isPresent()) { + throw new NetconfDocumentedException("Can't get child element with name: " + CONFIG_KEY, + ErrorType.application, + ErrorTag.unknown_element, + ErrorSeverity.error); + } + + return configChildNode.get(); + } + + @Override + protected String getOperationName() { + return OPERATION_NAME; + } +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Lock.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Lock.java new file mode 100644 index 0000000000..db912c5fc0 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Lock.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector.ops; + +import com.google.common.base.Optional; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException; +import org.opendaylight.controller.netconf.util.exception.UnexpectedNamespaceException; +import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class Lock extends AbstractLastNetconfOperation{ + + private static final Logger LOG = LoggerFactory.getLogger(Lock.class); + + private static final String OPERATION_NAME = "lock"; + private static final String TARGET_KEY = "target"; + + public Lock(final String netconfSessionIdForReporting) { + super(netconfSessionIdForReporting); + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + final Datastore targetDatastore = extractTargetParameter(operationElement); + if (targetDatastore == Datastore.candidate) { + LOG.debug("Locking candidate datastore on session: {}", getNetconfSessionIdForReporting()); + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + throw new NetconfDocumentedException("Unable to lock " + targetDatastore + " datastore", NetconfDocumentedException.ErrorType.application, + NetconfDocumentedException.ErrorTag.operation_not_supported, NetconfDocumentedException.ErrorSeverity.error); + } + + static Datastore extractTargetParameter(final XmlElement operationElement) throws NetconfDocumentedException { + final XmlElement targetChildNode; + try { + final XmlElement targetElement = operationElement.getOnlyChildElementWithSameNamespace(TARGET_KEY); + targetChildNode = targetElement.getOnlyChildElementWithSameNamespace(); + } catch (final MissingNameSpaceException | UnexpectedNamespaceException e) { + LOG.trace("Can't get only child element with same namespace", e); + throw NetconfDocumentedException.wrap(e); + } + + return Datastore.valueOf(targetChildNode.getName()); + } + + @Override + protected String getOperationName() { + return OPERATION_NAME; + } +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Unlock.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Unlock.java new file mode 100644 index 0000000000..2dd26633dd --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/Unlock.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector.ops; + +import com.google.common.base.Optional; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class Unlock extends AbstractLastNetconfOperation{ + + private static final Logger LOG = LoggerFactory.getLogger(Unlock.class); + + private static final String OPERATION_NAME = "unlock"; + private static final String TARGET_KEY = "target"; + + public Unlock(final String netconfSessionIdForReporting) { + super(netconfSessionIdForReporting); + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + final Datastore targetDatastore = Lock.extractTargetParameter(operationElement); + if (targetDatastore == Datastore.candidate) { + LOG.debug("Unlocking candidate datastore on session: {}", getNetconfSessionIdForReporting()); + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + throw new NetconfDocumentedException("Unable to unlock " + targetDatastore + " datastore", NetconfDocumentedException.ErrorType.application, + NetconfDocumentedException.ErrorTag.operation_not_supported, NetconfDocumentedException.ErrorSeverity.error); + } + + @Override + protected String getOperationName() { + return OPERATION_NAME; + } + +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/get/AbstractGet.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/get/AbstractGet.java new file mode 100644 index 0000000000..8f6ff417d6 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/get/AbstractGet.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector.ops.get; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Throwables; +import com.google.common.collect.Iterables; +import java.io.IOException; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.dom.DOMResult; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.mdsal.connector.CurrentSchemaContext; +import org.opendaylight.controller.netconf.mdsal.connector.ops.Datastore; +import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +public abstract class AbstractGet extends AbstractLastNetconfOperation { + + protected static final YangInstanceIdentifier ROOT = YangInstanceIdentifier.builder().build(); + + protected final CurrentSchemaContext schemaContext; + + + public AbstractGet(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext) { + super(netconfSessionIdForReporting); + this.schemaContext = schemaContext; + } + + private static final XMLOutputFactory XML_OUTPUT_FACTORY; + + static { + XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory(); + XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + } + + protected Node transformNormalizedNode(final Document document, final NormalizedNode data, final YangInstanceIdentifier dataRoot) { +// boolean isDataRoot = true; + + final DOMResult result = new DOMResult(document.createElement(XmlNetconfConstants.DATA_KEY)); + + final XMLStreamWriter xmlWriter = getXmlStreamWriter(result); + + final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, + schemaContext.getCurrentContext(), getSchemaPath(dataRoot)); + + final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter); + +// if (isDataRoot) { + writeRootElement(xmlWriter, nnWriter, (ContainerNode) data); +// } else { +// if (data instanceof MapEntryNode) { +// // Restconf allows returning one list item. We need to wrap it +// // in map node in order to serialize it properly +// data = ImmutableNodes.mapNodeBuilder(data.getNodeType()).addChild((MapEntryNode) data).build(); +// } +// nnWriter.write(data); +// nnWriter.flush(); +// } + return result.getNode(); + } + + private XMLStreamWriter getXmlStreamWriter(final DOMResult result) { + try { + return XML_OUTPUT_FACTORY.createXMLStreamWriter(result); + } catch (final XMLStreamException e) { + throw new RuntimeException(e); + } + } + + private static final Function PATH_ARG_TO_QNAME = new Function() { + @Override + public QName apply(final YangInstanceIdentifier.PathArgument input) { + return input.getNodeType(); + } + }; + + private SchemaPath getSchemaPath(final YangInstanceIdentifier dataRoot) { + return SchemaPath.create(Iterables.transform(dataRoot.getPathArguments(), PATH_ARG_TO_QNAME), dataRoot.equals(ROOT)); + } + + // TODO this code is located in Restconf already + private void writeRootElement(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) { + try { + final QName name = SchemaContext.NAME; + for (final DataContainerChild child : data.getValue()) { + nnWriter.write(child); + } + nnWriter.flush(); + xmlWriter.flush(); + } catch (XMLStreamException | IOException e) { + Throwables.propagate(e); + } + } + + protected static final class GetConfigExecution { + private final Optional datastore; + + public GetConfigExecution(final Optional datastore) { + this.datastore = datastore; + } + + public Optional getDatastore() { + return datastore; + } + + static GetConfigExecution fromXml(final XmlElement xml, final String operationName) throws NetconfDocumentedException { + try { + validateInputRpc(xml, operationName); + } catch (final NetconfDocumentedException e) { + throw new NetconfDocumentedException("Incorrect RPC: " + e.getMessage(), e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo()); + } + + final Optional sourceDatastore; + try { + sourceDatastore = parseSource(xml); + } catch (final NetconfDocumentedException e) { + throw new NetconfDocumentedException("Get-config source attribute error: " + e.getMessage(), e.getErrorType(), e.getErrorTag(), e.getErrorSeverity(), e.getErrorInfo()); + } + + // Add filter + + return new GetConfigExecution(sourceDatastore); + } + + private static Optional parseSource(final XmlElement xml) throws NetconfDocumentedException { + final Optional sourceElement = xml.getOnlyChildElementOptionally(XmlNetconfConstants.SOURCE_KEY, + XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + + return sourceElement.isPresent() ? + Optional.of(Datastore.valueOf(sourceElement.get().getOnlyChildElement().getName())) : Optional.absent(); + } + + private static void validateInputRpc(final XmlElement xml, String operationName) throws NetconfDocumentedException{ + xml.checkName(operationName); + xml.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0); + } + } + +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/get/Get.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/get/Get.java new file mode 100644 index 0000000000..cebd8c8883 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/get/Get.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector.ops.get; + +import com.google.common.base.Optional; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.mdsal.connector.CurrentSchemaContext; +import org.opendaylight.controller.netconf.mdsal.connector.TransactionProvider; +import org.opendaylight.controller.netconf.mdsal.connector.ops.Datastore; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class Get extends AbstractGet { + + private static final Logger LOG = LoggerFactory.getLogger(Get.class); + + private static final String OPERATION_NAME = "get"; + + private final TransactionProvider transactionProvider; + + public Get(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext, final TransactionProvider transactionProvider) { + super(netconfSessionIdForReporting, schemaContext); + this.transactionProvider = transactionProvider; + } + + @Override + protected Element handleWithNoSubsequentOperations(Document document, XmlElement operationElement) throws NetconfDocumentedException { + GetConfigExecution getConfigExecution = null; + try { + getConfigExecution = GetConfigExecution.fromXml(operationElement, OPERATION_NAME); + + } catch (final NetconfDocumentedException e) { + LOG.warn("Get request processing failed on session: {}", getNetconfSessionIdForReporting(), e); + throw e; + } + + final YangInstanceIdentifier dataRoot = ROOT; + DOMDataReadWriteTransaction rwTx = getTransaction(Datastore.running); + try { + final Optional> normalizedNodeOptional = rwTx.read(LogicalDatastoreType.OPERATIONAL, dataRoot).checkedGet(); + transactionProvider.abortRunningTransaction(rwTx); + return (Element) transformNormalizedNode(document, normalizedNodeOptional.get(), dataRoot); + } catch (ReadFailedException e) { + LOG.warn("Unable to read data: {}", dataRoot, e); + throw new IllegalStateException("Unable to read data " + dataRoot, e); + } + } + + private DOMDataReadWriteTransaction getTransaction(Datastore datastore) throws NetconfDocumentedException{ + if (datastore == Datastore.candidate) { + return transactionProvider.getOrCreateTransaction(); + } else if (datastore == Datastore.running) { + return transactionProvider.createRunningTransaction(); + } + throw new NetconfDocumentedException("Incorrect Datastore: ", ErrorType.protocol, ErrorTag.bad_element, ErrorSeverity.error); + } + + @Override + protected String getOperationName() { + return OPERATION_NAME; + } +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/get/GetConfig.java b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/get/GetConfig.java new file mode 100644 index 0000000000..f2d8abbb61 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/controller/netconf/mdsal/connector/ops/get/GetConfig.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.mdsal.connector.ops.get; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorSeverity; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorTag; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException.ErrorType; +import org.opendaylight.controller.netconf.mdsal.connector.CurrentSchemaContext; +import org.opendaylight.controller.netconf.mdsal.connector.TransactionProvider; +import org.opendaylight.controller.netconf.mdsal.connector.ops.Datastore; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class GetConfig extends AbstractGet { + + private static final Logger LOG = LoggerFactory.getLogger(GetConfig.class); + + private static final String OPERATION_NAME = "get-config"; + + private final TransactionProvider transactionProvider; + + public GetConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext, final TransactionProvider transactionProvider) { + super(netconfSessionIdForReporting, schemaContext); + this.transactionProvider = transactionProvider; + } + + @Override + protected Element handleWithNoSubsequentOperations(Document document, XmlElement operationElement) throws NetconfDocumentedException { + GetConfigExecution getConfigExecution = null; + try { + getConfigExecution = GetConfigExecution.fromXml(operationElement, OPERATION_NAME); + + } catch (final NetconfDocumentedException e) { + LOG.warn("Get request processing failed on session: {}", getNetconfSessionIdForReporting(), e); + throw e; + } + + final YangInstanceIdentifier dataRoot = ROOT; + // Proper exception should be thrown + Preconditions.checkState(getConfigExecution.getDatastore().isPresent(), "Source element missing from request"); + + DOMDataReadWriteTransaction rwTx = getTransaction(getConfigExecution.getDatastore().get()); + try { + final Optional> normalizedNodeOptional = rwTx.read(LogicalDatastoreType.CONFIGURATION, dataRoot).checkedGet(); + if (getConfigExecution.getDatastore().get() == Datastore.running) { + transactionProvider.abortRunningTransaction(rwTx); + rwTx = null; + } + return (Element) transformNormalizedNode(document, normalizedNodeOptional.get(), dataRoot); + } catch (ReadFailedException e) { + LOG.warn("Unable to read data: {}", dataRoot, e); + throw new IllegalStateException("Unable to read data " + dataRoot, e); + } + } + + private DOMDataReadWriteTransaction getTransaction(Datastore datastore) throws NetconfDocumentedException{ + if (datastore == Datastore.candidate) { + return transactionProvider.getOrCreateTransaction(); + } else if (datastore == Datastore.running) { + return transactionProvider.createRunningTransaction(); + } + throw new NetconfDocumentedException("Incorrect Datastore: ", ErrorType.protocol, ErrorTag.bad_element, ErrorSeverity.error); + } + + @Override + protected String getOperationName() { + return OPERATION_NAME; + } + +} diff --git a/opendaylight/netconf/mdsal-netconf-connector/src/main/yang/netconf-mdsal-mapper.yang b/opendaylight/netconf/mdsal-netconf-connector/src/main/yang/netconf-mdsal-mapper.yang new file mode 100644 index 0000000000..9d9966e8f1 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-connector/src/main/yang/netconf-mdsal-mapper.yang @@ -0,0 +1,59 @@ +module netconf-mdsal-mapper { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:netconf:mdsal:mapper"; + prefix "nmm"; + + import netconf-northbound-mapper { prefix nnm; revision-date 2015-01-14; } + import opendaylight-md-sal-dom { prefix md-sal-dom; revision-date 2013-10-28; } + import config { prefix config; revision-date 2013-04-05; } + + organization "Cisco Systems, Inc."; + + description + "This module contains the base YANG definitions for + an MD-SAL mapper implementation"; + + revision "2015-01-14" { + description + "Initial revision."; + } + + identity netconf-mdsal-mapper { + base config:module-type; + config:provided-service nnm:netconf-northbound-mapper; + } + + augment "/config:modules/config:module/config:configuration" { + case netconf-mdsal-mapper { + when "/config:modules/config:module/config:type = 'netconf-mdsal-mapper'"; + + container root-schema-service { + uses config:service-ref { + refine type { + mandatory false; + config:required-identity md-sal-dom:schema-service; + } + } + } + + container dom-broker { + uses config:service-ref { + refine type { + mandatory false; + config:required-identity md-sal-dom:dom-async-data-broker; + } + } + } + + container mapper-aggregator { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity nnm:netconf-mapper-registry; + } + } + } + } + } + +} diff --git a/opendaylight/netconf/mdsal-netconf-monitoring/pom.xml b/opendaylight/netconf/mdsal-netconf-monitoring/pom.xml new file mode 100644 index 0000000000..3e78dd19e3 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-monitoring/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + org.opendaylight.controller + netconf-subsystem + 0.3.0-SNAPSHOT + + mdsal-netconf-monitoring + bundle + ${project.artifactId} + + + + + ${project.groupId} + netconf-api + + + ${project.groupId} + netconf-mapping-api + + + ${project.groupId} + netconf-monitoring + + + ${project.groupId} + netconf-util + + + org.opendaylight.controller + sal-binding-config + + + + com.google.guava + guava + + + + org.opendaylight.yangtools + mockito-configuration + + + org.opendaylight.yangtools.model + ietf-inet-types + + + org.slf4j + slf4j-api + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.yangtools + yang-maven-plugin + + + config + + generate-sources + + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + ${jmxGeneratorPath} + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + ${salGeneratorPath} + + + true + + + + + + org.opendaylight.controller + yang-jmx-generator-plugin + ${config.version} + + + + + + + diff --git a/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/monitoring/MonitoringToMdsalWriter.java b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/monitoring/MonitoringToMdsalWriter.java new file mode 100644 index 0000000000..50958e423f --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/monitoring/MonitoringToMdsalWriter.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.config.yang.netconf.mdsal.monitoring; + +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import org.opendaylight.controller.md.sal.binding.api.DataBroker; +import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; +import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; +import org.opendaylight.controller.sal.binding.api.BindingAwareProvider; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class MonitoringToMdsalWriter implements AutoCloseable, NetconfMonitoringService.MonitoringListener, BindingAwareProvider { + + private static final Logger LOG = LoggerFactory.getLogger(MonitoringToMdsalWriter.class); + + private final NetconfMonitoringService serverMonitoringDependency; + private DataBroker dataBroker; + + public MonitoringToMdsalWriter(final NetconfMonitoringService serverMonitoringDependency) { + this.serverMonitoringDependency = serverMonitoringDependency; + } + + @Override + public void close() { + final WriteTransaction tx = dataBroker.newWriteOnlyTransaction(); + tx.delete(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.create(NetconfState.class)); + final CheckedFuture submit = tx.submit(); + + Futures.addCallback(submit, new FutureCallback() { + @Override + public void onSuccess(final Void aVoid) { + LOG.debug("Netconf state cleared successfully"); + } + + @Override + public void onFailure(final Throwable throwable) { + LOG.warn("Unable to clear netconf state", throwable); + } + }); + } + + @Override + public void onStateChanged(final NetconfState state) { + Preconditions.checkState(dataBroker != null); + final WriteTransaction tx = dataBroker.newWriteOnlyTransaction(); + tx.put(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.create(NetconfState.class), state); + // FIXME first attempt (right after we register to binding broker) always fails + // Is it due to the fact that we are writing from the onSessionInitiated callback ? + final CheckedFuture submit = tx.submit(); + + Futures.addCallback(submit, new FutureCallback() { + @Override + public void onSuccess(final Void aVoid) { + LOG.debug("Netconf state updated successfully"); + } + + @Override + public void onFailure(final Throwable throwable) { + LOG.warn("Unable to update netconf state", throwable); + } + }); + } + + @Override + public void onSessionInitiated(final BindingAwareBroker.ProviderContext providerContext) { + dataBroker = providerContext.getSALService(DataBroker.class); + serverMonitoringDependency.registerListener(this); + } +} diff --git a/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/monitoring/NetconfMdsalMonitoringMapperModule.java b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/monitoring/NetconfMdsalMonitoringMapperModule.java new file mode 100644 index 0000000000..dadc0f493b --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/monitoring/NetconfMdsalMonitoringMapperModule.java @@ -0,0 +1,149 @@ +package org.opendaylight.controller.config.yang.netconf.mdsal.monitoring; + +import com.google.common.base.Optional; +import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.monitoring.GetSchema; +import org.opendaylight.controller.netconf.monitoring.MonitoringConstants; + +public class NetconfMdsalMonitoringMapperModule extends org.opendaylight.controller.config.yang.netconf.mdsal.monitoring.AbstractNetconfMdsalMonitoringMapperModule { + public NetconfMdsalMonitoringMapperModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { + super(identifier, dependencyResolver); + } + + public NetconfMdsalMonitoringMapperModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, final org.opendaylight.controller.config.yang.netconf.mdsal.monitoring.NetconfMdsalMonitoringMapperModule oldModule, final java.lang.AutoCloseable oldInstance) { + super(identifier, dependencyResolver, oldModule, oldInstance); + } + + @Override + public void customValidation() { + // add custom validation form module attributes here. + } + + @Override + public java.lang.AutoCloseable createInstance() { + final NetconfMonitoringService serverMonitoringDependency = getServerMonitoringDependency(); + + final MonitoringToMdsalWriter monitoringToMdsalWriter = new MonitoringToMdsalWriter(serverMonitoringDependency); + getBindingAwareBrokerDependency().registerProvider(monitoringToMdsalWriter); + + final MdSalMonitoringMapperFactory mdSalMonitoringMapperFactory = new MdSalMonitoringMapperFactory(new MdsalMonitoringMapper(serverMonitoringDependency)) { + @Override + public void close() { + super.close(); + monitoringToMdsalWriter.close(); + getAggregatorDependency().onRemoveNetconfOperationServiceFactory(this); + } + }; + + getAggregatorDependency().onAddNetconfOperationServiceFactory(mdSalMonitoringMapperFactory); + return mdSalMonitoringMapperFactory; + + } + + // FIXME almost exactly same code as in netconf-monitoring, refactor + private static class MdSalMonitoringMapperFactory implements NetconfOperationServiceFactory, AutoCloseable { + + private final NetconfOperationService operationService; + + private static final Set CAPABILITIES = Sets.newHashSet(new Capability() { + + @Override + public String getCapabilityUri() { + return MonitoringConstants.URI; + } + + @Override + public Optional getModuleNamespace() { + return Optional.of(MonitoringConstants.NAMESPACE); + } + + @Override + public Optional getModuleName() { + return Optional.of(MonitoringConstants.MODULE_NAME); + } + + @Override + public Optional getRevision() { + return Optional.of(MonitoringConstants.MODULE_REVISION); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.absent(); + } + + @Override + public Collection getLocation() { + return Collections.emptyList(); + } + }); + + private static final AutoCloseable AUTO_CLOSEABLE = new AutoCloseable() { + @Override + public void close() throws Exception { + // NOOP + } + }; + + private final List listeners = new ArrayList<>(); + + public MdSalMonitoringMapperFactory(final NetconfOperationService operationService) { + this.operationService = operationService; + } + + @Override + public NetconfOperationService createService(final String netconfSessionIdForReporting) { + return operationService; + } + + @Override + public Set getCapabilities() { + return CAPABILITIES; + } + + @Override + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + listener.onCapabilitiesAdded(getCapabilities()); + listeners.add(listener); + return AUTO_CLOSEABLE; + } + + @Override + public void close() { + for (final CapabilityListener listener : listeners) { + listener.onCapabilitiesRemoved(getCapabilities()); + } + } + } + + + private static class MdsalMonitoringMapper implements NetconfOperationService { + + private final NetconfMonitoringService serverMonitoringDependency; + + public MdsalMonitoringMapper(final NetconfMonitoringService serverMonitoringDependency) { + this.serverMonitoringDependency = serverMonitoringDependency; + } + + @Override + public Set getNetconfOperations() { + return Collections.singleton(new GetSchema(serverMonitoringDependency)); + } + + @Override + public void close() { + // NOOP + } + } +} diff --git a/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/monitoring/NetconfMdsalMonitoringMapperModuleFactory.java b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/monitoring/NetconfMdsalMonitoringMapperModuleFactory.java new file mode 100644 index 0000000000..e0d459318f --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/config/yang/netconf/mdsal/monitoring/NetconfMdsalMonitoringMapperModuleFactory.java @@ -0,0 +1,13 @@ +/* +* Generated file +* +* Generated from: yang module name: netconf-mdsal-monitoring yang module local name: netconf-mdsal-monitoring-mapper +* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator +* Generated at: Wed Feb 18 10:22:17 CET 2015 +* +* Do not modify this file unless it is present under src/main directory +*/ +package org.opendaylight.controller.config.yang.netconf.mdsal.monitoring; +public class NetconfMdsalMonitoringMapperModuleFactory extends org.opendaylight.controller.config.yang.netconf.mdsal.monitoring.AbstractNetconfMdsalMonitoringMapperModuleFactory { + +} diff --git a/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/NetconfMonitoringOperationService.java b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/NetconfMonitoringOperationService.java new file mode 100644 index 0000000000..9ae4df429c --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/NetconfMonitoringOperationService.java @@ -0,0 +1,35 @@ +/* +* 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.monitoring; + +import com.google.common.collect.Sets; +import java.util.Set; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; +import org.opendaylight.controller.netconf.monitoring.Get; +import org.opendaylight.controller.netconf.monitoring.GetSchema; + +public class NetconfMonitoringOperationService implements NetconfOperationService { + + private final NetconfMonitoringService monitor; + + public NetconfMonitoringOperationService(final NetconfMonitoringService monitor) { + this.monitor = monitor; + } + + @Override + public Set getNetconfOperations() { + return Sets.newHashSet(new Get(monitor), new GetSchema(monitor)); + } + + @Override + public void close() { + } + +} diff --git a/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/NetconfMonitoringOperationServiceFactory.java b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/NetconfMonitoringOperationServiceFactory.java new file mode 100644 index 0000000000..78c23688e4 --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/NetconfMonitoringOperationServiceFactory.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.monitoring; + +import com.google.common.base.Optional; +import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; + +/** +* Created by mmarsale on 18.2.2015. +*/ +public class NetconfMonitoringOperationServiceFactory implements NetconfOperationServiceFactory, AutoCloseable { + + private final NetconfMonitoringOperationService operationService; + + private static final Set CAPABILITIES = Sets.newHashSet(new Capability() { + + @Override + public String getCapabilityUri() { + return MonitoringConstants.URI; + } + + @Override + public Optional getModuleNamespace() { + return Optional.of(MonitoringConstants.NAMESPACE); + } + + @Override + public Optional getModuleName() { + return Optional.of(MonitoringConstants.MODULE_NAME); + } + + @Override + public Optional getRevision() { + return Optional.of(MonitoringConstants.MODULE_REVISION); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.absent(); + } + + @Override + public Collection getLocation() { + return Collections.emptyList(); + } + }); + + private static final AutoCloseable AUTO_CLOSEABLE = new AutoCloseable() { + @Override + public void close() throws Exception { + // NOOP + } + }; + + private final List listeners = new ArrayList<>(); + + public NetconfMonitoringOperationServiceFactory(final NetconfMonitoringOperationService operationService) { + this.operationService = operationService; + } + + @Override + public NetconfOperationService createService(final String netconfSessionIdForReporting) { + return operationService; + } + + @Override + public Set getCapabilities() { + return CAPABILITIES; + } + + @Override + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + listener.onCapabilitiesAdded(getCapabilities()); + listeners.add(listener); + return AUTO_CLOSEABLE; + } + + @Override + public void close() { + for (final CapabilityListener listener : listeners) { + listener.onCapabilitiesRemoved(getCapabilities()); + } + } +} diff --git a/opendaylight/netconf/mdsal-netconf-monitoring/src/main/yang/netconf-mdsal-monitoring.yang b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/yang/netconf-mdsal-monitoring.yang new file mode 100644 index 0000000000..68a248e48e --- /dev/null +++ b/opendaylight/netconf/mdsal-netconf-monitoring/src/main/yang/netconf-mdsal-monitoring.yang @@ -0,0 +1,60 @@ +module netconf-mdsal-monitoring { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:netconf:mdsal:monitoring"; + prefix "nmmonitor"; + + import netconf-northbound-mapper { prefix nnm; revision-date 2015-01-14; } + import opendaylight-md-sal-binding {prefix md-sal-binding; revision-date 2013-10-28;} + import netconf-northbound { prefix nn; revision-date 2015-01-14; } + import config { prefix config; revision-date 2013-04-05; } + + organization "Cisco Systems, Inc."; + + description + "This module contains the base YANG definitions for + an MD-SAL monitoring mapper implementation"; + + revision "2015-02-18" { + description + "Initial revision."; + } + + identity netconf-mdsal-monitoring-mapper { + base config:module-type; + config:provided-service nnm:netconf-northbound-mapper; + } + + augment "/config:modules/config:module/config:configuration" { + case netconf-mdsal-monitoring-mapper { + when "/config:modules/config:module/config:type = 'netconf-mdsal-monitoring-mapper'"; + + container server-monitoring { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity nn:netconf-server-monitoring; + } + } + } + + container aggregator { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity nnm:netconf-mapper-registry; + } + } + } + + container binding-aware-broker { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity md-sal-binding:binding-broker-osgi-registry; + } + } + } + } + } + +} diff --git a/opendaylight/netconf/netconf-api/pom.xml b/opendaylight/netconf/netconf-api/pom.xml index 18bbce4e30..965747c2ea 100644 --- a/opendaylight/netconf/netconf-api/pom.xml +++ b/opendaylight/netconf/netconf-api/pom.xml @@ -43,15 +43,42 @@ org.apache.felix maven-bundle-plugin - - - org.opendaylight.controller.netconf.api, - org.opendaylight.controller.netconf.api.jmx, - org.opendaylight.controller.netconf.api.xml, - org.opendaylight.controller.netconf.api.monitoring, - - + + org.opendaylight.yangtools + yang-maven-plugin + + + config + + generate-sources + + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + ${jmxGeneratorPath} + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + ${salGeneratorPath} + + + true + + + + + + org.opendaylight.controller + yang-jmx-generator-plugin + ${config.version} + + + diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/Capability.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/Capability.java similarity index 93% rename from opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/Capability.java rename to opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/Capability.java index 408756bf4d..6a061b1ea9 100644 --- a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/Capability.java +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/Capability.java @@ -6,7 +6,7 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.mapping.api; +package org.opendaylight.controller.netconf.api; import com.google.common.base.Optional; import java.util.Collection; diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerDispatcher.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerDispatcher.java new file mode 100644 index 0000000000..6bf21c1d5d --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/NetconfServerDispatcher.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.api; + +import io.netty.channel.ChannelFuture; +import io.netty.channel.local.LocalAddress; +import java.net.InetSocketAddress; + +public interface NetconfServerDispatcher { + + ChannelFuture createServer(InetSocketAddress address); + + ChannelFuture createLocalServer(LocalAddress address); +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/CapabilityListener.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/CapabilityListener.java new file mode 100644 index 0000000000..5d9468c8ea --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/CapabilityListener.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.api.monitoring; + +import java.util.Set; +import org.opendaylight.controller.netconf.api.Capability; + +public interface CapabilityListener { + + void onCapabilitiesAdded(Set addedCaps); + + void onCapabilitiesRemoved(Set removedCaps); +} diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/NetconfMonitoringService.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/NetconfMonitoringService.java index 51eea9307d..d22412c7cf 100644 --- a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/NetconfMonitoringService.java +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/NetconfMonitoringService.java @@ -7,12 +7,30 @@ */ package org.opendaylight.controller.netconf.api.monitoring; +import com.google.common.base.Optional; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Sessions; -public interface NetconfMonitoringService { +public interface NetconfMonitoringService extends CapabilityListener, SessionListener { Sessions getSessions(); Schemas getSchemas(); + + String getSchemaForCapability(String moduleName, Optional revision); + + Capabilities getCapabilities(); + + /** + * Allows push based state information transfer. After the listener is registered, current state is pushed to the listener. + */ + AutoCloseable registerListener(MonitoringListener listener); + + interface MonitoringListener { + + // TODO more granular updates would make sense + void onStateChanged(NetconfState state); + } } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/SessionMonitoringService.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/SessionListener.java similarity index 59% rename from opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/SessionMonitoringService.java rename to opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/SessionListener.java index 7a0b8b7170..a11fb879ca 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/SessionMonitoringService.java +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/monitoring/SessionListener.java @@ -1,16 +1,17 @@ /* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.impl.osgi; -import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession; - -public interface SessionMonitoringService { +package org.opendaylight.controller.netconf.api.monitoring; +/** + * Created by mmarsale on 13.2.2015. + */ +public interface SessionListener { void onSessionUp(NetconfManagementSession session); void onSessionDown(NetconfManagementSession session); diff --git a/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/util/NetconfConstants.java b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/util/NetconfConstants.java new file mode 100644 index 0000000000..b9c4dcaf4a --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/java/org/opendaylight/controller/netconf/api/util/NetconfConstants.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.api.util; + +public final class NetconfConstants { + /* + * TODO define marker interface in mapping-api that the serviceFactories in cofing subsystem + * will implement so we can check for services with instanceof instead of constants + */ + public static final String SERVICE_NAME = "name"; + public static final String CONFIG_NETCONF_CONNECTOR = "config-netconf-connector"; + public static final String NETCONF_MONITORING = "ietf-netconf-monitoring"; +} diff --git a/opendaylight/netconf/netconf-api/src/main/yang/netconf-northbound.yang b/opendaylight/netconf/netconf-api/src/main/yang/netconf-northbound.yang new file mode 100644 index 0000000000..e2b0d35867 --- /dev/null +++ b/opendaylight/netconf/netconf-api/src/main/yang/netconf-northbound.yang @@ -0,0 +1,27 @@ +module netconf-northbound { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:config:netconf:northbound"; + prefix "nn"; + + import config { prefix config; revision-date 2013-04-05; } + + description + "This module contains the base YANG definitions for + netconf northbound server API"; + + revision "2015-01-14" { + description + "Initial revision."; + } + + identity netconf-server-dispatcher { + base "config:service-type"; + config:java-class "org.opendaylight.controller.netconf.api.NetconfServerDispatcher"; + } + + identity netconf-server-monitoring { + base "config:service-type"; + config:java-class "org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService"; + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-artifacts/pom.xml b/opendaylight/netconf/netconf-artifacts/pom.xml index eb3cac18df..d27ea55812 100644 --- a/opendaylight/netconf/netconf-artifacts/pom.xml +++ b/opendaylight/netconf/netconf-artifacts/pom.xml @@ -62,6 +62,16 @@ netconf-connector-config ${project.version} + + ${project.groupId} + netconf-mdsal-config + ${project.version} + + + ${project.groupId} + mdsal-netconf-connector + ${project.version} + ${project.groupId} netconf-impl @@ -77,6 +87,11 @@ netconf-monitoring ${project.version} + + ${project.groupId} + mdsal-netconf-monitoring + ${project.version} + ${project.groupId} netconf-netty-util @@ -108,6 +123,12 @@ ${project.version} + + ${project.groupId} + ietf-netconf + ${project.version} + + ${project.groupId} ietf-netconf-monitoring @@ -119,6 +140,22 @@ ${project.version} + + ${project.groupId} + ietf-netconf-notifications + ${project.version} + + + ${project.groupId} + netconf-notifications-api + ${project.version} + + + ${project.groupId} + netconf-notifications-impl + ${project.version} + + ${project.groupId} netconf-client diff --git a/opendaylight/netconf/netconf-cli/pom.xml b/opendaylight/netconf/netconf-cli/pom.xml index c292d93206..e1226a5dc4 100644 --- a/opendaylight/netconf/netconf-cli/pom.xml +++ b/opendaylight/netconf/netconf-cli/pom.xml @@ -65,6 +65,10 @@ org.opendaylight.yangtools yang-parser-impl + + org.opendaylight.controller + sal-netconf-connector + 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 index 64397de118..8c38ee29e9 100644 --- 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 @@ -70,7 +70,7 @@ public class Main { } case SSH: { writeStatus(consoleIO, "Connecting to %s via SSH. Please wait.", cliArgs.getAddress()); - connectionManager.connectBlocking(cliArgs.getAddress(), getClientSshConfig(cliArgs)); + connectionManager.connectBlocking(cliArgs.getAddress(), cliArgs.getServerAddress(), getClientSshConfig(cliArgs)); break; } case NONE: {/* Do not connect initially */ 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 index d5c9dc6fc7..bede549536 100644 --- 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 @@ -14,7 +14,7 @@ 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.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.core.api.RpcImplementation; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; @@ -23,7 +23,7 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; * Implementation of RemoteDeviceHandler. Integrates cli with * sal-netconf-connector. */ -public class NetconfDeviceConnectionHandler implements RemoteDeviceHandler { +public class NetconfDeviceConnectionHandler implements RemoteDeviceHandler { private final CommandDispatcher commandDispatcher; private final SchemaContextRegistry schemaContextRegistry; @@ -42,7 +42,7 @@ public class NetconfDeviceConnectionHandler implements RemoteDeviceHandler connectBlocking(final String name, final NetconfClientConfigurationBuilder configBuilder) { - this.connect(name, configBuilder); + public synchronized Set connectBlocking(final String name, final InetSocketAddress address, final NetconfClientConfigurationBuilder configBuilder) { + this.connect(name, address, configBuilder); synchronized (handler) { while (handler.isUp() == false) { try { 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 index f702aa3805..54706b8cb9 100644 --- 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 @@ -53,11 +53,11 @@ public class Connect extends AbstractCommand { @Override public Output invoke(final Input inputArgs) { final NetconfClientConfigurationBuilder config = getConfig(inputArgs); - return invoke(config, getArgument(inputArgs, "address-name", String.class)); + return invoke(config, getArgument(inputArgs, "address-name", String.class), inputArgs); } - private Output invoke(final NetconfClientConfigurationBuilder config, final String addressName) { - final Set remoteCmds = connectManager.connectBlocking(addressName, config); + private Output invoke(final NetconfClientConfigurationBuilder config, final String addressName, final Input inputArgs) { + final Set remoteCmds = connectManager.connectBlocking(addressName, getAdress(inputArgs), config); final ArrayList> output = Lists.newArrayList(); output.add(new SimpleNodeTOImpl<>(QName.create(getCommandId(), "status"), null, "Connection initiated")); @@ -92,6 +92,17 @@ public class Connect extends AbstractCommand { .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH); } + private InetSocketAddress getAdress(final Input inputArgs) { + final String address = getArgument(inputArgs, "address-name", String.class); + final InetSocketAddress inetAddress; + try { + inetAddress = new InetSocketAddress(InetAddress.getByName(address), getArgument(inputArgs, "address-port", Integer.class)); + } catch (final UnknownHostException e) { + throw new IllegalArgumentException("Unable to use address: " + address, e); + } + return inetAddress; + } + 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); diff --git a/opendaylight/netconf/netconf-config/src/main/resources/initial/01-netconf.xml b/opendaylight/netconf/netconf-config/src/main/resources/initial/01-netconf.xml index f81a332ab6..06a5e65149 100644 --- a/opendaylight/netconf/netconf-config/src/main/resources/initial/01-netconf.xml +++ b/opendaylight/netconf/netconf-config/src/main/resources/initial/01-netconf.xml @@ -49,6 +49,17 @@ global-netconf-processing-executor-threadfactory + + + prefix:threadpool-scheduled + global-netconf-ssh-scheduled-executor + 8 + + + prefix:threadfactory + global-netconf-processing-executor-threadfactory + + @@ -72,6 +83,10 @@ global-netconf-processing-executor /modules/module[type='threadpool-flexible'][name='global-netconf-processing-executor'] + + global-netconf-ssh-scheduled-executor + /modules/module[type='threadpool-scheduled'][name='global-netconf-ssh-scheduled-executor'] + @@ -81,5 +96,6 @@ urn:opendaylight:params:xml:ns:yang:controller:config:netconf:client:dispatcher?module=odl-netconfig-client-cfg&revision=2014-04-08 urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl?module=threadpool-impl&revision=2013-04-05 urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl:flexible?module=threadpool-impl-flexible&revision=2013-12-01 + urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl:scheduled?module=threadpool-impl-scheduled&revision=2013-12-01 diff --git a/opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml b/opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml index 7155eb8883..28c61b9946 100644 --- a/opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml +++ b/opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml @@ -20,6 +20,7 @@ admin admin false + true prefix:netty-event-executor global-event-executor @@ -41,7 +42,7 @@ global-netconf-processing-executor - + diff --git a/opendaylight/netconf/netconf-impl/pom.xml b/opendaylight/netconf/netconf-impl/pom.xml index 310073fc72..6f19731f01 100644 --- a/opendaylight/netconf/netconf-impl/pom.xml +++ b/opendaylight/netconf/netconf-impl/pom.xml @@ -110,6 +110,7 @@ org.opendaylight.controller.netconf.impl.osgi.NetconfImplActivator + org.opendaylight.controller.netconf.impl.* @@ -125,6 +126,41 @@ + + org.opendaylight.yangtools + yang-maven-plugin + + + config + + generate-sources + + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + ${jmxGeneratorPath} + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + ${salGeneratorPath} + + + true + + + + + + org.opendaylight.controller + yang-jmx-generator-plugin + ${config.version} + + + diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfMapperAggregatorModule.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfMapperAggregatorModule.java new file mode 100644 index 0000000000..284c600f0c --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfMapperAggregatorModule.java @@ -0,0 +1,22 @@ +package org.opendaylight.controller.config.yang.config.netconf.northbound.impl; + +import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory; + +public class NetconfMapperAggregatorModule extends org.opendaylight.controller.config.yang.config.netconf.northbound.impl.AbstractNetconfMapperAggregatorModule { + public NetconfMapperAggregatorModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { + super(identifier, dependencyResolver); + } + + public NetconfMapperAggregatorModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, final org.opendaylight.controller.config.yang.config.netconf.northbound.impl.NetconfMapperAggregatorModule oldModule, final java.lang.AutoCloseable oldInstance) { + super(identifier, dependencyResolver, oldModule, oldInstance); + } + + @Override + public void customValidation() {} + + @Override + public java.lang.AutoCloseable createInstance() { + return new AggregatedNetconfOperationServiceFactory(); + } + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfMapperAggregatorModuleFactory.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfMapperAggregatorModuleFactory.java new file mode 100644 index 0000000000..0e415bdfcb --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfMapperAggregatorModuleFactory.java @@ -0,0 +1,13 @@ +/* +* Generated file +* +* Generated from: yang module name: netconf-northbound-impl yang module local name: netconf-mapper-aggregator +* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator +* Generated at: Tue Feb 17 17:24:19 CET 2015 +* +* Do not modify this file unless it is present under src/main directory +*/ +package org.opendaylight.controller.config.yang.config.netconf.northbound.impl; +public class NetconfMapperAggregatorModuleFactory extends org.opendaylight.controller.config.yang.config.netconf.northbound.impl.AbstractNetconfMapperAggregatorModuleFactory { + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerDispatcherModule.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerDispatcherModule.java new file mode 100644 index 0000000000..3c476608a2 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerDispatcherModule.java @@ -0,0 +1,55 @@ +package org.opendaylight.controller.config.yang.config.netconf.northbound.impl; + +import org.opendaylight.controller.config.api.JmxAttributeValidationException; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; +import org.opendaylight.controller.netconf.impl.CommitNotifier; +import org.opendaylight.controller.netconf.impl.NetconfServerDispatcherImpl; +import org.opendaylight.controller.netconf.impl.NetconfServerSessionNegotiatorFactory; +import org.opendaylight.controller.netconf.impl.SessionIdProvider; +import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; + +public class NetconfServerDispatcherModule extends org.opendaylight.controller.config.yang.config.netconf.northbound.impl.AbstractNetconfServerDispatcherModule { + public NetconfServerDispatcherModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { + super(identifier, dependencyResolver); + } + + public NetconfServerDispatcherModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.controller.config.yang.config.netconf.northbound.impl.NetconfServerDispatcherModule oldModule, java.lang.AutoCloseable oldInstance) { + super(identifier, dependencyResolver, oldModule, oldInstance); + } + + @Override + public void customValidation() { + JmxAttributeValidationException.checkCondition(getConnectionTimeoutMillis() > 0, "Invalid connection timeout", connectionTimeoutMillisJmxAttribute); + } + + @Override + public java.lang.AutoCloseable createInstance() { + + final AggregatedNetconfOperationServiceFactory aggregatedOpProvider = getAggregatedOpProvider(); + final NetconfMonitoringService monitoringService = getServerMonitorDependency(); + final NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory( + getTimerDependency(), aggregatedOpProvider, new SessionIdProvider(), getConnectionTimeoutMillis(), CommitNotifier.NoopCommitNotifier.getInstance(), monitoringService); + final NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer( + serverNegotiatorFactory); + + return new NetconfServerDispatcherImpl(serverChannelInitializer, getBossThreadGroupDependency(), getWorkerThreadGroupDependency()) { + + @Override + public void close() { + // NOOP, close should not be present here, the deprecated method closes injected evet loop groups + } + }; + + } + + private AggregatedNetconfOperationServiceFactory getAggregatedOpProvider() { + final AggregatedNetconfOperationServiceFactory netconfOperationProvider = new AggregatedNetconfOperationServiceFactory(); + for (final NetconfOperationServiceFactory netconfOperationServiceFactory : getMappersDependency()) { + netconfOperationProvider.onAddNetconfOperationServiceFactory(netconfOperationServiceFactory); + } + return netconfOperationProvider; + } + + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerDispatcherModuleFactory.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerDispatcherModuleFactory.java new file mode 100644 index 0000000000..de44caddac --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerDispatcherModuleFactory.java @@ -0,0 +1,13 @@ +/* +* Generated file +* +* Generated from: yang module name: netconf-northbound-impl yang module local name: netconf-server-dispatcher-impl +* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator +* Generated at: Thu Feb 12 11:32:29 CET 2015 +* +* Do not modify this file unless it is present under src/main directory +*/ +package org.opendaylight.controller.config.yang.config.netconf.northbound.impl; +public class NetconfServerDispatcherModuleFactory extends org.opendaylight.controller.config.yang.config.netconf.northbound.impl.AbstractNetconfServerDispatcherModuleFactory { + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerMonitoringModule.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerMonitoringModule.java new file mode 100644 index 0000000000..dc18cd3687 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerMonitoringModule.java @@ -0,0 +1,24 @@ +package org.opendaylight.controller.config.yang.config.netconf.northbound.impl; + +import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl; + +public class NetconfServerMonitoringModule extends org.opendaylight.controller.config.yang.config.netconf.northbound.impl.AbstractNetconfServerMonitoringModule { + public NetconfServerMonitoringModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { + super(identifier, dependencyResolver); + } + + public NetconfServerMonitoringModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.controller.config.yang.config.netconf.northbound.impl.NetconfServerMonitoringModule oldModule, java.lang.AutoCloseable oldInstance) { + super(identifier, dependencyResolver, oldModule, oldInstance); + } + + @Override + public void customValidation() { + // add custom validation form module attributes here. + } + + @Override + public java.lang.AutoCloseable createInstance() { + return new NetconfMonitoringServiceImpl(getAggregatorDependency()); + } + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerMonitoringModuleFactory.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerMonitoringModuleFactory.java new file mode 100644 index 0000000000..fe74486a77 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/config/yang/config/netconf/northbound/impl/NetconfServerMonitoringModuleFactory.java @@ -0,0 +1,13 @@ +/* +* Generated file +* +* Generated from: yang module name: netconf-northbound-impl yang module local name: netconf-server-monitoring-impl +* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator +* Generated at: Tue Feb 17 17:24:19 CET 2015 +* +* Do not modify this file unless it is present under src/main directory +*/ +package org.opendaylight.controller.config.yang.config.netconf.northbound.impl; +public class NetconfServerMonitoringModuleFactory extends org.opendaylight.controller.config.yang.config.netconf.northbound.impl.AbstractNetconfServerMonitoringModuleFactory { + +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/CapabilityProviderImpl.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/CapabilityProviderImpl.java deleted file mode 100644 index 13cc973ba7..0000000000 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/CapabilityProviderImpl.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.impl; - -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.collect.Maps; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; -import org.opendaylight.controller.netconf.mapping.api.Capability; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CapabilityProviderImpl implements CapabilityProvider { - private final NetconfOperationServiceSnapshot netconfOperationServiceSnapshot; - private final Set capabilityURIs; - - private static final Logger LOG = LoggerFactory.getLogger(CapabilityProviderImpl.class); - - public CapabilityProviderImpl(NetconfOperationServiceSnapshot netconfOperationServiceSnapshot) { - this.netconfOperationServiceSnapshot = netconfOperationServiceSnapshot; - Map urisToCapabilitiesInternalMap = getCapabilitiesInternal(netconfOperationServiceSnapshot); - List capabilityURIs = new ArrayList<>(urisToCapabilitiesInternalMap.keySet()); - Collections.sort(capabilityURIs); - this.capabilityURIs = Collections.unmodifiableSet(new TreeSet<>(capabilityURIs)); - } - - private static Map getCapabilitiesInternal( - NetconfOperationServiceSnapshot netconfOperationServiceSnapshot) { - Map capabilityMap = Maps.newHashMap(); - - for (NetconfOperationService netconfOperationService : netconfOperationServiceSnapshot.getServices()) { - final Set caps = netconfOperationService.getCapabilities(); - - for (Capability cap : caps) { - - if(capabilityMap.containsKey(cap.getCapabilityUri())) { - LOG.debug("Duplicate capability {} from service {}", cap.getCapabilityUri(), netconfOperationService); - } - - capabilityMap.put(cap.getCapabilityUri(), cap); - } - } - - return capabilityMap; - } - - @Override - public synchronized String getSchemaForCapability(String moduleName, Optional revision) { - - Map> mappedModulesToRevisionToSchema = Maps.newHashMap(); - - for (NetconfOperationService netconfOperationService : netconfOperationServiceSnapshot.getServices()) { - final Set caps = netconfOperationService.getCapabilities(); - - for (Capability cap : caps) { - if (!cap.getModuleName().isPresent() - || !cap.getRevision().isPresent() - || !cap.getCapabilitySchema().isPresent()){ - continue; - } - - final String currentModuleName = cap.getModuleName().get(); - Map revisionMap = mappedModulesToRevisionToSchema.get(currentModuleName); - if (revisionMap == null) { - revisionMap = Maps.newHashMap(); - mappedModulesToRevisionToSchema.put(currentModuleName, revisionMap); - } - - String currentRevision = cap.getRevision().get(); - revisionMap.put(currentRevision, cap.getCapabilitySchema().get()); - } - } - - Map revisionMapRequest = mappedModulesToRevisionToSchema.get(moduleName); - Preconditions.checkState(revisionMapRequest != null, "Capability for module %s not present, " + "" - + "available modules : %s", moduleName, capabilityURIs); - - if (revision.isPresent()) { - String schema = revisionMapRequest.get(revision.get()); - - Preconditions.checkState(schema != null, - "Capability for module %s:%s not present, available revisions for module: %s", moduleName, - revision.get(), revisionMapRequest.keySet()); - - return schema; - } else { - Preconditions.checkState(revisionMapRequest.size() == 1, - "Expected 1 capability for module %s, available revisions : %s", moduleName, - revisionMapRequest.keySet()); - return revisionMapRequest.values().iterator().next(); - } - } - - @Override - public synchronized Set getCapabilities() { - return capabilityURIs; - } - -} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/CommitNotifier.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/CommitNotifier.java new file mode 100644 index 0000000000..d9f8e34da9 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/CommitNotifier.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.impl; + +import java.util.Set; +import org.w3c.dom.Element; + +public interface CommitNotifier { + void sendCommitNotification(String message, Element cfgSnapshot, Set capabilities); + + public static final class NoopCommitNotifier implements CommitNotifier { + + private static final CommitNotifier INSTANCE = new NoopCommitNotifier(); + + private NoopCommitNotifier() {} + + public static CommitNotifier getInstance() { + return INSTANCE; + } + + @Override + public void sendCommitNotification(final String message, final Element cfgSnapshot, final Set capabilities) { + // NOOP + } + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/DefaultCommitNotificationProducer.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/DefaultCommitNotificationProducer.java index ab37bac683..88ff928c51 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/DefaultCommitNotificationProducer.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/DefaultCommitNotificationProducer.java @@ -24,7 +24,7 @@ import org.slf4j.LoggerFactory; import org.w3c.dom.Element; public class DefaultCommitNotificationProducer extends NotificationBroadcasterSupport implements - DefaultCommitOperationMXBean, AutoCloseable { + DefaultCommitOperationMXBean, AutoCloseable, CommitNotifier { private static final Logger LOG = LoggerFactory.getLogger(DefaultCommitNotificationProducer.class); @@ -46,6 +46,7 @@ public class DefaultCommitNotificationProducer extends NotificationBroadcasterSu } } + @Override public void sendCommitNotification(String message, Element cfgSnapshot, Set capabilities) { CommitJMXNotification notif = NetconfJMXNotification.afterCommit(this, message, cfgSnapshot, capabilities); LOG.debug("Notification about commit {} sent", notif); diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcherImpl.java similarity index 86% rename from opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java rename to opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcherImpl.java index 4dfb749818..a040b8b87b 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcher.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerDispatcherImpl.java @@ -8,7 +8,6 @@ package org.opendaylight.controller.netconf.impl; -import com.google.common.annotations.VisibleForTesting; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; @@ -18,23 +17,23 @@ import io.netty.channel.local.LocalServerChannel; import io.netty.channel.socket.SocketChannel; import io.netty.util.concurrent.Promise; import java.net.InetSocketAddress; +import org.opendaylight.controller.netconf.api.NetconfServerDispatcher; import org.opendaylight.controller.netconf.impl.util.DeserializerExceptionHandler; import org.opendaylight.controller.netconf.nettyutil.AbstractChannelInitializer; import org.opendaylight.protocol.framework.AbstractDispatcher; -public class NetconfServerDispatcher extends AbstractDispatcher { +public class NetconfServerDispatcherImpl extends AbstractDispatcher implements NetconfServerDispatcher { private final ServerChannelInitializer initializer; - public NetconfServerDispatcher(ServerChannelInitializer serverChannelInitializer, EventLoopGroup bossGroup, - EventLoopGroup workerGroup) { + public NetconfServerDispatcherImpl(ServerChannelInitializer serverChannelInitializer, EventLoopGroup bossGroup, + EventLoopGroup workerGroup) { super(bossGroup, workerGroup); this.initializer = serverChannelInitializer; } - @VisibleForTesting + @Override public ChannelFuture createServer(InetSocketAddress address) { - return super.createServer(address, new PipelineInitializer() { @Override public void initializeChannel(final SocketChannel ch, final Promise promise) { @@ -43,6 +42,7 @@ public class NetconfServerDispatcher extends AbstractDispatcher() { @Override diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java index 8f2c39df06..0cf2dbc281 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSession.java @@ -55,9 +55,9 @@ public final class NetconfServerSession extends AbstractNetconfSession { private static final Logger LOG = LoggerFactory.getLogger(NetconfServerSessionListener.class); - private final SessionMonitoringService monitoringService; + private final NetconfMonitoringService monitoringService; private final NetconfOperationRouter operationRouter; private final AutoCloseable onSessionDownCloseable; - public NetconfServerSessionListener(final NetconfOperationRouter operationRouter, final SessionMonitoringService monitoringService, + public NetconfServerSessionListener(final NetconfOperationRouter operationRouter, NetconfMonitoringService monitoringService, final AutoCloseable onSessionDownCloseable) { this.operationRouter = operationRouter; this.monitoringService = monitoringService; @@ -45,6 +45,8 @@ public class NetconfServerSessionListener implements NetconfSessionListener { - - private final DefaultCommitNotificationProducer commitNotifier; - private final SessionMonitoringService monitor; - private final NetconfOperationServiceSnapshot netconfOperationServiceSnapshot; - private final CapabilityProvider capabilityProvider; - - public NetconfServerSessionListenerFactory(final DefaultCommitNotificationProducer commitNotifier, - final SessionMonitoringService monitor, - final NetconfOperationServiceSnapshot netconfOperationServiceSnapshot, - final CapabilityProvider capabilityProvider) { - - this.commitNotifier = commitNotifier; - this.monitor = monitor; - this.netconfOperationServiceSnapshot = netconfOperationServiceSnapshot; - this.capabilityProvider = capabilityProvider; - } - - @Override - public NetconfServerSessionListener getSessionListener() { - NetconfOperationRouter operationRouter = new NetconfOperationRouterImpl(netconfOperationServiceSnapshot, capabilityProvider, commitNotifier); - return new NetconfServerSessionListener(operationRouter, monitor, netconfOperationServiceSnapshot); - } -} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java index 34f4f0e653..cf489608ca 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/NetconfServerSessionNegotiatorFactory.java @@ -8,8 +8,6 @@ package org.opendaylight.controller.netconf.impl; -import static org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider.NetconfOperationProviderUtil.getNetconfSessionIdForReporting; - import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -19,11 +17,13 @@ import io.netty.util.concurrent.Promise; import java.util.Set; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; import org.opendaylight.controller.netconf.api.NetconfServerSessionPreferences; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; -import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; -import org.opendaylight.controller.netconf.impl.osgi.SessionMonitoringService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot; +import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCommit; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouter; +import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouterImpl; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessage; import org.opendaylight.protocol.framework.SessionListenerFactory; import org.opendaylight.protocol.framework.SessionNegotiator; @@ -42,28 +42,28 @@ public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorF private final Timer timer; private final SessionIdProvider idProvider; - private final NetconfOperationProvider netconfOperationProvider; + private final NetconfOperationServiceFactory aggregatedOpService; private final long connectionTimeoutMillis; - private final DefaultCommitNotificationProducer commitNotificationProducer; - private final SessionMonitoringService monitoringService; + private final CommitNotifier commitNotificationProducer; + private final NetconfMonitoringService monitoringService; private static final Logger LOG = LoggerFactory.getLogger(NetconfServerSessionNegotiatorFactory.class); private final Set baseCapabilities; // TODO too many params, refactor - public NetconfServerSessionNegotiatorFactory(Timer timer, NetconfOperationProvider netconfOperationProvider, - SessionIdProvider idProvider, long connectionTimeoutMillis, - DefaultCommitNotificationProducer commitNot, - SessionMonitoringService monitoringService) { + public NetconfServerSessionNegotiatorFactory(final Timer timer, final NetconfOperationServiceFactory netconfOperationProvider, + final SessionIdProvider idProvider, final long connectionTimeoutMillis, + final CommitNotifier commitNot, + final NetconfMonitoringService monitoringService) { this(timer, netconfOperationProvider, idProvider, connectionTimeoutMillis, commitNot, monitoringService, DEFAULT_BASE_CAPABILITIES); } // TODO too many params, refactor - public NetconfServerSessionNegotiatorFactory(Timer timer, NetconfOperationProvider netconfOperationProvider, - SessionIdProvider idProvider, long connectionTimeoutMillis, - DefaultCommitNotificationProducer commitNot, - SessionMonitoringService monitoringService, Set baseCapabilities) { + public NetconfServerSessionNegotiatorFactory(final Timer timer, final NetconfOperationServiceFactory netconfOperationProvider, + final SessionIdProvider idProvider, final long connectionTimeoutMillis, + final CommitNotifier commitNot, + final NetconfMonitoringService monitoringService, final Set baseCapabilities) { this.timer = timer; - this.netconfOperationProvider = netconfOperationProvider; + this.aggregatedOpService = netconfOperationProvider; this.idProvider = idProvider; this.connectionTimeoutMillis = connectionTimeoutMillis; this.commitNotificationProducer = commitNot; @@ -73,12 +73,12 @@ public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorF private ImmutableSet validateBaseCapabilities(final Set baseCapabilities) { // Check base capabilities to be supported by the server - Sets.SetView unknownBaseCaps = Sets.difference(baseCapabilities, DEFAULT_BASE_CAPABILITIES); + final Sets.SetView unknownBaseCaps = Sets.difference(baseCapabilities, DEFAULT_BASE_CAPABILITIES); Preconditions.checkArgument(unknownBaseCaps.isEmpty(), "Base capabilities that will be supported by netconf server have to be subset of %s, unknown base capabilities: %s", DEFAULT_BASE_CAPABILITIES, unknownBaseCaps); - ImmutableSet.Builder b = ImmutableSet.builder(); + final ImmutableSet.Builder b = ImmutableSet.builder(); b.addAll(baseCapabilities); // Base 1.0 capability is supported by default b.add(XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0); @@ -95,32 +95,33 @@ public class NetconfServerSessionNegotiatorFactory implements SessionNegotiatorF * @return session negotiator */ @Override - public SessionNegotiator getSessionNegotiator(SessionListenerFactory defunctSessionListenerFactory, - Channel channel, Promise promise) { - long sessionId = idProvider.getNextSessionId(); - NetconfOperationServiceSnapshot netconfOperationServiceSnapshot = netconfOperationProvider.openSnapshot( - getNetconfSessionIdForReporting(sessionId)); - CapabilityProvider capabilityProvider = new CapabilityProviderImpl(netconfOperationServiceSnapshot); - - NetconfServerSessionPreferences proposal = null; + public SessionNegotiator getSessionNegotiator(final SessionListenerFactory defunctSessionListenerFactory, + final Channel channel, final Promise promise) { + final long sessionId = idProvider.getNextSessionId(); + + NetconfServerSessionPreferences proposal; try { - proposal = new NetconfServerSessionPreferences( - createHelloMessage(sessionId, capabilityProvider), sessionId); - } catch (NetconfDocumentedException e) { - LOG.error("Unable to create hello mesage for session {} with capability provider {}", sessionId,capabilityProvider); + proposal = new NetconfServerSessionPreferences(createHelloMessage(sessionId, monitoringService), sessionId); + } catch (final NetconfDocumentedException e) { + LOG.error("Unable to create hello message for session {} with {}", sessionId, monitoringService); throw new IllegalStateException(e); } - NetconfServerSessionListenerFactory sessionListenerFactory = new NetconfServerSessionListenerFactory( - commitNotificationProducer, monitoringService, - netconfOperationServiceSnapshot, capabilityProvider); - return new NetconfServerSessionNegotiator(proposal, promise, channel, timer, - sessionListenerFactory.getSessionListener(), connectionTimeoutMillis); + getListener(Long.toString(sessionId)), connectionTimeoutMillis); + } + + private NetconfServerSessionListener getListener(final String netconfSessionIdForReporting) { + final NetconfOperationService service = + this.aggregatedOpService.createService(netconfSessionIdForReporting); + final NetconfOperationRouter operationRouter = + new NetconfOperationRouterImpl(service, commitNotificationProducer, monitoringService, netconfSessionIdForReporting); + return new NetconfServerSessionListener(operationRouter, monitoringService, service); + } - private NetconfHelloMessage createHelloMessage(long sessionId, CapabilityProvider capabilityProvider) throws NetconfDocumentedException { - return NetconfHelloMessage.createServerHello(Sets.union(capabilityProvider.getCapabilities(), baseCapabilities), sessionId); + private NetconfHelloMessage createHelloMessage(final long sessionId, final NetconfMonitoringService capabilityProvider) throws NetconfDocumentedException { + return NetconfHelloMessage.createServerHello(Sets.union(DefaultCommit.transformCapabilities(capabilityProvider.getCapabilities()), baseCapabilities), sessionId); } } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/CapabilityProvider.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/CapabilityProvider.java deleted file mode 100644 index 60cde27fe5..0000000000 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/CapabilityProvider.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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.impl.mapping; - -import com.google.common.base.Optional; -import java.util.Set; - -public interface CapabilityProvider { - - String getSchemaForCapability(String moduleName, Optional revision); - - Set getCapabilities(); - -} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java index fbe855f8be..8b2c02bcd4 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommit.java @@ -8,18 +8,24 @@ package org.opendaylight.controller.netconf.impl.mapping.operations; +import com.google.common.base.Function; import com.google.common.base.Preconditions; +import com.google.common.collect.Collections2; +import com.google.common.collect.Sets; import java.io.InputStream; +import java.util.Set; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; -import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; -import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; +import org.opendaylight.controller.netconf.impl.CommitNotifier; import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouter; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation; import org.opendaylight.controller.netconf.util.xml.XmlElement; import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -31,11 +37,11 @@ public class DefaultCommit extends AbstractNetconfOperation { private static final String NOTIFY_ATTR = "notify"; - private final DefaultCommitNotificationProducer notificationProducer; - private final CapabilityProvider cap; + private final CommitNotifier notificationProducer; + private final NetconfMonitoringService cap; private final NetconfOperationRouter operationRouter; - public DefaultCommit(DefaultCommitNotificationProducer notifier, CapabilityProvider cap, + public DefaultCommit(CommitNotifier notifier, NetconfMonitoringService cap, String netconfSessionIdForReporting, NetconfOperationRouter netconfOperationRouter) { super(netconfSessionIdForReporting); this.notificationProducer = notifier; @@ -73,12 +79,22 @@ public class DefaultCommit extends AbstractNetconfOperation { removePersisterAttributes(requestMessage); Element cfgSnapshot = getConfigSnapshot(operationRouter); LOG.debug("Config snapshot retrieved successfully {}", cfgSnapshot); - notificationProducer.sendCommitNotification("ok", cfgSnapshot, cap.getCapabilities()); + notificationProducer.sendCommitNotification("ok", cfgSnapshot, transformCapabilities(cap.getCapabilities())); } return subsequentOperation.execute(requestMessage); } + // FIXME move somewhere to util since this is required also by negotiatiorFactory + public static Set transformCapabilities(final Capabilities capabilities) { + return Sets.newHashSet(Collections2.transform(capabilities.getCapability(), new Function() { + @Override + public String apply(final Uri uri) { + return uri.getValue(); + } + })); + } + @Override protected Element handle(Document document, XmlElement message, NetconfOperationChainedExecution subsequentOperation) throws NetconfDocumentedException { throw new UnsupportedOperationException("Never gets called"); diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/AggregatedNetconfOperationServiceFactory.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/AggregatedNetconfOperationServiceFactory.java new file mode 100644 index 0000000000..ae68ecc120 --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/AggregatedNetconfOperationServiceFactory.java @@ -0,0 +1,140 @@ +/* + * 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.impl.osgi; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactoryListener; +import org.opendaylight.controller.netconf.util.CloseableUtil; + +/** + * NetconfOperationService aggregator. Makes a collection of operation services accessible as one. + */ +public class AggregatedNetconfOperationServiceFactory implements NetconfOperationServiceFactory, NetconfOperationServiceFactoryListener, AutoCloseable { + + private final Set factories = new HashSet<>(); + private final Multimap registrations = HashMultimap.create(); + private final Set listeners = Sets.newHashSet(); + + @Override + public synchronized void onAddNetconfOperationServiceFactory(NetconfOperationServiceFactory service) { + factories.add(service); + + for (final CapabilityListener listener : listeners) { + AutoCloseable reg = service.registerCapabilityListener(listener); + registrations.put(service, reg); + } + } + + @Override + public synchronized void onRemoveNetconfOperationServiceFactory(NetconfOperationServiceFactory service) { + factories.remove(service); + + for (final AutoCloseable autoCloseable : registrations.get(service)) { + try { + autoCloseable.close(); + } catch (Exception e) { + // FIXME Issue warning + } + } + + registrations.removeAll(service); + } + + @Override + public synchronized Set getCapabilities() { + final HashSet capabilities = Sets.newHashSet(); + for (final NetconfOperationServiceFactory factory : factories) { + capabilities.addAll(factory.getCapabilities()); + } + return capabilities; + } + + @Override + public synchronized AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + final Map regs = Maps.newHashMap(); + + for (final NetconfOperationServiceFactory factory : factories) { + final AutoCloseable reg = factory.registerCapabilityListener(listener); + regs.put(factory, reg); + } + listeners.add(listener); + + return new AutoCloseable() { + @Override + public void close() throws Exception { + synchronized (AggregatedNetconfOperationServiceFactory.this) { + listeners.remove(listener); + CloseableUtil.closeAll(regs.values()); + for (final Map.Entry reg : regs.entrySet()) { + registrations.remove(reg.getKey(), reg.getValue()); + } + } + } + }; + } + + @Override + public synchronized NetconfOperationService createService(final String netconfSessionIdForReporting) { + return new AggregatedNetconfOperation(factories, netconfSessionIdForReporting); + } + + @Override + public synchronized void close() throws Exception { + factories.clear(); + for (AutoCloseable reg : registrations.values()) { + reg.close(); + } + registrations.clear(); + listeners.clear(); + } + + private static final class AggregatedNetconfOperation implements NetconfOperationService { + + private final Set services; + + public AggregatedNetconfOperation(final Set factories, final String netconfSessionIdForReporting) { + final Builder b = ImmutableSet.builder(); + for (final NetconfOperationServiceFactory factory : factories) { + b.add(factory.createService(netconfSessionIdForReporting)); + } + this.services = b.build(); + } + + @Override + public Set getNetconfOperations() { + final HashSet operations = Sets.newHashSet(); + for (final NetconfOperationService service : services) { + operations.addAll(service.getNetconfOperations()); + } + return operations; + } + + @Override + public void close() { + try { + CloseableUtil.closeAll(services); + } catch (final Exception e) { + throw new IllegalStateException("Unable to properly close all aggregated services", e); + } + } + } +} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java index 27423c09b7..1e35597d9a 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivator.java @@ -16,10 +16,10 @@ import java.util.Hashtable; import java.util.concurrent.TimeUnit; import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; -import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher; +import org.opendaylight.controller.netconf.impl.NetconfServerDispatcherImpl; import org.opendaylight.controller.netconf.impl.NetconfServerSessionNegotiatorFactory; import org.opendaylight.controller.netconf.impl.SessionIdProvider; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactoryListener; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; @@ -40,7 +40,7 @@ public class NetconfImplActivator implements BundleActivator { @Override public void start(final BundleContext context) { - NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); + AggregatedNetconfOperationServiceFactory factoriesListener = new AggregatedNetconfOperationServiceFactory(); startOperationServiceFactoryTracker(context, factoriesListener); SessionIdProvider idProvider = new SessionIdProvider(); @@ -50,31 +50,28 @@ public class NetconfImplActivator implements BundleActivator { commitNot = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer()); - SessionMonitoringService monitoringService = startMonitoringService(context, factoriesListener); + NetconfMonitoringService monitoringService = startMonitoringService(context, factoriesListener); NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory( timer, factoriesListener, idProvider, connectionTimeoutMillis, commitNot, monitoringService); eventLoopGroup = new NioEventLoopGroup(); - NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer( + NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer( serverNegotiatorFactory); - NetconfServerDispatcher dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup); + NetconfServerDispatcherImpl dispatch = new NetconfServerDispatcherImpl(serverChannelInitializer, eventLoopGroup, eventLoopGroup); LocalAddress address = NetconfConfigUtil.getNetconfLocalAddress(); LOG.trace("Starting local netconf server at {}", address); dispatch.createLocalServer(address); - - context.registerService(NetconfOperationProvider.class, factoriesListener, null); - } - private void startOperationServiceFactoryTracker(BundleContext context, NetconfOperationServiceFactoryListenerImpl factoriesListener) { + private void startOperationServiceFactoryTracker(BundleContext context, NetconfOperationServiceFactoryListener factoriesListener) { factoriesTracker = new NetconfOperationServiceFactoryTracker(context, factoriesListener); factoriesTracker.open(); } - private NetconfMonitoringServiceImpl startMonitoringService(BundleContext context, NetconfOperationServiceFactoryListenerImpl factoriesListener) { + private NetconfMonitoringServiceImpl startMonitoringService(BundleContext context, AggregatedNetconfOperationServiceFactory factoriesListener) { NetconfMonitoringServiceImpl netconfMonitoringServiceImpl = new NetconfMonitoringServiceImpl(factoriesListener); Dictionary dic = new Hashtable<>(); regMonitoring = context.registerService(NetconfMonitoringService.class, netconfMonitoringServiceImpl, dic); diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfMonitoringServiceImpl.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfMonitoringServiceImpl.java index efbe3ad68f..b02137b748 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfMonitoringServiceImpl.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfMonitoringServiceImpl.java @@ -8,25 +8,32 @@ package org.opendaylight.controller.netconf.impl.osgi; 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.ImmutableList; import com.google.common.collect.ImmutableList.Builder; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import io.netty.util.internal.ConcurrentSet; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; +import org.opendaylight.controller.netconf.api.Capability; import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession; import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; -import org.opendaylight.controller.netconf.mapping.api.Capability; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfStateBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Yang; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.CapabilitiesBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.SchemasBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Sessions; @@ -38,7 +45,8 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.mon import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class NetconfMonitoringServiceImpl implements NetconfMonitoringService, SessionMonitoringService { +public class NetconfMonitoringServiceImpl implements NetconfMonitoringService, AutoCloseable { + private static final Schema.Location NETCONF_LOCATION = new Schema.Location(Schema.Location.Enumeration.NETCONF); private static final List NETCONF_LOCATIONS = ImmutableList.of(NETCONF_LOCATION); private static final Logger LOG = LoggerFactory.getLogger(NetconfMonitoringServiceImpl.class); @@ -48,67 +56,142 @@ public class NetconfMonitoringServiceImpl implements NetconfMonitoringService, S return input.toManagementSession(); } }; + private static final Function CAPABILITY_TO_URI = new Function() { + @Override + public Uri apply(final Capability input) { + return new Uri(input.getCapabilityUri()); + } + }; private final Set sessions = new ConcurrentSet<>(); - private final NetconfOperationProvider netconfOperationProvider; + private final NetconfOperationServiceFactory netconfOperationProvider; + private final Map capabilities = new ConcurrentHashMap<>(); + + private final Set listeners = Sets.newHashSet(); - public NetconfMonitoringServiceImpl(final NetconfOperationProvider netconfOperationProvider) { + public NetconfMonitoringServiceImpl(final NetconfOperationServiceFactory netconfOperationProvider) { this.netconfOperationProvider = netconfOperationProvider; + netconfOperationProvider.registerCapabilityListener(this); } @Override - public void onSessionUp(final NetconfManagementSession session) { + public synchronized void onSessionUp(final NetconfManagementSession session) { LOG.debug("Session {} up", session); Preconditions.checkState(!sessions.contains(session), "Session %s was already added", session); sessions.add(session); + notifyListeners(); } @Override - public void onSessionDown(final NetconfManagementSession session) { + public synchronized void onSessionDown(final NetconfManagementSession session) { LOG.debug("Session {} down", session); Preconditions.checkState(sessions.contains(session), "Session %s not present", session); sessions.remove(session); + notifyListeners(); } @Override - public Sessions getSessions() { + public synchronized Sessions getSessions() { return new SessionsBuilder().setSession(ImmutableList.copyOf(Collections2.transform(sessions, SESSION_FUNCTION))).build(); } @Override - public Schemas getSchemas() { - // capabilities should be split from operations (it will allow to move getSchema operation to monitoring module) - try (NetconfOperationServiceSnapshot snapshot = netconfOperationProvider.openSnapshot("netconf-monitoring")) { - return transformSchemas(snapshot.getServices()); - } catch (RuntimeException e) { + public synchronized Schemas getSchemas() { + try { + return transformSchemas(netconfOperationProvider.getCapabilities()); + } catch (final RuntimeException e) { throw e; - } catch (Exception e) { + } catch (final Exception e) { throw new IllegalStateException("Exception while closing", e); } } - private static Schemas transformSchemas(final Set services) { - // FIXME: Capability implementations do not have hashcode/equals! - final Set caps = new HashSet<>(); - for (NetconfOperationService netconfOperationService : services) { - // TODO check for duplicates ? move capability merging to snapshot - // Split capabilities from operations first and delete this duplicate code - caps.addAll(netconfOperationService.getCapabilities()); + @Override + public synchronized String getSchemaForCapability(final String moduleName, final Optional revision) { + + // FIXME not effective at all + + Map> mappedModulesToRevisionToSchema = Maps.newHashMap(); + + final Collection caps = capabilities.values(); + + for (Capability cap : caps) { + if (!cap.getModuleName().isPresent() + || !cap.getRevision().isPresent() + || !cap.getCapabilitySchema().isPresent()){ + continue; + } + + final String currentModuleName = cap.getModuleName().get(); + Map revisionMap = mappedModulesToRevisionToSchema.get(currentModuleName); + if (revisionMap == null) { + revisionMap = Maps.newHashMap(); + mappedModulesToRevisionToSchema.put(currentModuleName, revisionMap); + } + + String currentRevision = cap.getRevision().get(); + revisionMap.put(currentRevision, cap.getCapabilitySchema().get()); } + Map revisionMapRequest = mappedModulesToRevisionToSchema.get(moduleName); + Preconditions.checkState(revisionMapRequest != null, "Capability for module %s not present, " + "" + + "available modules : %s", moduleName, Collections2.transform(caps, CAPABILITY_TO_URI)); + + if (revision.isPresent()) { + String schema = revisionMapRequest.get(revision.get()); + + Preconditions.checkState(schema != null, + "Capability for module %s:%s not present, available revisions for module: %s", moduleName, + revision.get(), revisionMapRequest.keySet()); + + return schema; + } else { + Preconditions.checkState(revisionMapRequest.size() == 1, + "Expected 1 capability for module %s, available revisions : %s", moduleName, + revisionMapRequest.keySet()); + return revisionMapRequest.values().iterator().next(); + } + } + + @Override + public synchronized Capabilities getCapabilities() { + return new CapabilitiesBuilder().setCapability(Lists.newArrayList(capabilities.keySet())).build(); + } + + @Override + public synchronized AutoCloseable registerListener(final MonitoringListener listener) { + listeners.add(listener); + listener.onStateChanged(getCurrentNetconfState()); + return new AutoCloseable() { + @Override + public void close() throws Exception { + listeners.remove(listener); + } + }; + } + + private NetconfState getCurrentNetconfState() { + return new NetconfStateBuilder() + .setCapabilities(getCapabilities()) + .setSchemas(getSchemas()) + .setSessions(getSessions()) + .build(); + } + + private static Schemas transformSchemas(final Set caps) { final List schemas = new ArrayList<>(caps.size()); - for (Capability cap : caps) { + for (final Capability cap : caps) { if (cap.getCapabilitySchema().isPresent()) { - SchemaBuilder builder = new SchemaBuilder(); + final SchemaBuilder builder = new SchemaBuilder(); Preconditions.checkState(cap.getModuleNamespace().isPresent()); builder.setNamespace(new Uri(cap.getModuleNamespace().get())); Preconditions.checkState(cap.getRevision().isPresent()); - String version = cap.getRevision().get(); + final String version = cap.getRevision().get(); builder.setVersion(version); Preconditions.checkState(cap.getModuleName().isPresent()); - String identifier = cap.getModuleName().get(); + final String identifier = cap.getModuleName().get(); builder.setIdentifier(identifier); builder.setFormat(Yang.class); @@ -132,10 +215,38 @@ public class NetconfMonitoringServiceImpl implements NetconfMonitoringService, S final Builder b = ImmutableList.builder(); b.add(NETCONF_LOCATION); - for (String location : locations) { + for (final String location : locations) { b.add(new Schema.Location(new Uri(location))); } return b.build(); } + + @Override + public synchronized void onCapabilitiesAdded(final Set addedCaps) { + // FIXME howto check for duplicates + this.capabilities.putAll(Maps.uniqueIndex(addedCaps, CAPABILITY_TO_URI)); + notifyListeners(); + } + + private void notifyListeners() { + for (final MonitoringListener listener : listeners) { + listener.onStateChanged(getCurrentNetconfState()); + } + } + + @Override + public synchronized void onCapabilitiesRemoved(final Set addedCaps) { + for (final Capability addedCap : addedCaps) { + capabilities.remove(addedCap.getCapabilityUri()); + } + notifyListeners(); + } + + @Override + public synchronized void close() throws Exception { + listeners.clear(); + sessions.clear(); + capabilities.clear(); + } } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java index aeab13f7e2..9d58bd911c 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationRouterImpl.java @@ -17,12 +17,11 @@ import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; -import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; +import org.opendaylight.controller.netconf.impl.CommitNotifier; import org.opendaylight.controller.netconf.impl.NetconfServerSession; -import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCloseSession; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultCommit; -import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultGetSchema; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultNetconfOperation; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultStartExi; import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultStopExi; @@ -30,7 +29,7 @@ import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot; +import org.opendaylight.controller.netconf.mapping.api.SessionAwareNetconfOperation; import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,29 +38,20 @@ import org.w3c.dom.Document; public class NetconfOperationRouterImpl implements NetconfOperationRouter { private static final Logger LOG = LoggerFactory.getLogger(NetconfOperationRouterImpl.class); - private final NetconfOperationServiceSnapshot netconfOperationServiceSnapshot; + private final NetconfOperationService netconfOperationServiceSnapshot; private final Collection allNetconfOperations; - public NetconfOperationRouterImpl(final NetconfOperationServiceSnapshot netconfOperationServiceSnapshot, final CapabilityProvider capabilityProvider, - final DefaultCommitNotificationProducer commitNotifier) { + public NetconfOperationRouterImpl(final NetconfOperationService netconfOperationServiceSnapshot, + final CommitNotifier commitNotifier, final NetconfMonitoringService netconfMonitoringService, final String sessionId) { this.netconfOperationServiceSnapshot = Preconditions.checkNotNull(netconfOperationServiceSnapshot); - final String sessionId = netconfOperationServiceSnapshot.getNetconfSessionIdForReporting(); - final Set ops = new HashSet<>(); - ops.add(new DefaultGetSchema(capabilityProvider, sessionId)); ops.add(new DefaultCloseSession(sessionId, this)); ops.add(new DefaultStartExi(sessionId)); ops.add(new DefaultStopExi(sessionId)); - ops.add(new DefaultCommit(commitNotifier, capabilityProvider, sessionId, this)); + ops.add(new DefaultCommit(commitNotifier, netconfMonitoringService, sessionId, this)); - for (NetconfOperationService netconfOperationService : netconfOperationServiceSnapshot.getServices()) { - for (NetconfOperation netconfOperation : netconfOperationService.getNetconfOperations()) { - Preconditions.checkState(!ops.contains(netconfOperation), - "Netconf operation %s already present", netconfOperation); - ops.add(netconfOperation); - } - } + ops.addAll(netconfOperationServiceSnapshot.getNetconfOperations()); allNetconfOperations = ImmutableSet.copyOf(ops); } @@ -147,11 +137,14 @@ public class NetconfOperationRouterImpl implements NetconfOperationRouter { if (netconfOperation instanceof DefaultNetconfOperation) { ((DefaultNetconfOperation) netconfOperation).setNetconfSession(session); } + if(netconfOperation instanceof SessionAwareNetconfOperation) { + ((SessionAwareNetconfOperation) netconfOperation).setSession(session); + } if (!handlingPriority.equals(HandlingPriority.CANNOT_HANDLE)) { Preconditions.checkState(!sortedPriority.containsKey(handlingPriority), - "Multiple %s available to handle message %s with priority %s", - NetconfOperation.class.getName(), message, handlingPriority); + "Multiple %s available to handle message %s with priority %s, %s and %s", + NetconfOperation.class.getName(), message, handlingPriority, netconfOperation, sortedPriority.get(handlingPriority)); sortedPriority.put(handlingPriority, netconfOperation); } } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListenerImpl.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListenerImpl.java deleted file mode 100644 index 6c55c35e25..0000000000 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListenerImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.impl.osgi; - -import java.util.HashSet; -import java.util.Set; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; - -public class NetconfOperationServiceFactoryListenerImpl implements NetconfOperationServiceFactoryListener, - NetconfOperationProvider { - private final Set allFactories = new HashSet<>(); - - @Override - public synchronized void onAddNetconfOperationServiceFactory(NetconfOperationServiceFactory service) { - allFactories.add(service); - } - - @Override - public synchronized void onRemoveNetconfOperationServiceFactory(NetconfOperationServiceFactory service) { - allFactories.remove(service); - } - - @Override - public synchronized NetconfOperationServiceSnapshotImpl openSnapshot(String sessionIdForReporting) { - return new NetconfOperationServiceSnapshotImpl(allFactories, sessionIdForReporting); - } - -} diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTracker.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTracker.java index c1d9317c29..d97ac90922 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTracker.java +++ b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTracker.java @@ -7,7 +7,9 @@ */ package org.opendaylight.controller.netconf.impl.osgi; +import org.opendaylight.controller.netconf.api.util.NetconfConstants; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactoryListener; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; @@ -24,15 +26,24 @@ class NetconfOperationServiceFactoryTracker extends @Override public NetconfOperationServiceFactory addingService(ServiceReference reference) { - NetconfOperationServiceFactory netconfOperationServiceFactory = super.addingService(reference); - factoriesListener.onAddNetconfOperationServiceFactory(netconfOperationServiceFactory); - return netconfOperationServiceFactory; + Object property = reference.getProperty(NetconfConstants.SERVICE_NAME); + if (property != null + && (property.equals(NetconfConstants.CONFIG_NETCONF_CONNECTOR) + || property.equals(NetconfConstants.NETCONF_MONITORING))) { + NetconfOperationServiceFactory netconfOperationServiceFactory = super.addingService(reference); + factoriesListener.onAddNetconfOperationServiceFactory(netconfOperationServiceFactory); + return netconfOperationServiceFactory; + } + + return null; } @Override public void removedService(ServiceReference reference, NetconfOperationServiceFactory netconfOperationServiceFactory) { - factoriesListener.onRemoveNetconfOperationServiceFactory(netconfOperationServiceFactory); + if (netconfOperationServiceFactory != null) { + factoriesListener.onRemoveNetconfOperationServiceFactory(netconfOperationServiceFactory); + } } } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceSnapshotImpl.java b/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceSnapshotImpl.java deleted file mode 100644 index 26abdd974d..0000000000 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceSnapshotImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.impl.osgi; - -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSet.Builder; -import java.util.Set; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot; -import org.opendaylight.controller.netconf.util.CloseableUtil; - -public class NetconfOperationServiceSnapshotImpl implements NetconfOperationServiceSnapshot { - - private final Set services; - private final String netconfSessionIdForReporting; - - public NetconfOperationServiceSnapshotImpl(final Set factories, final String sessionIdForReporting) { - final Builder b = ImmutableSet.builder(); - netconfSessionIdForReporting = sessionIdForReporting; - for (NetconfOperationServiceFactory factory : factories) { - b.add(factory.createService(netconfSessionIdForReporting)); - } - this.services = b.build(); - } - - @Override - public String getNetconfSessionIdForReporting() { - return netconfSessionIdForReporting; - } - - @Override - public Set getServices() { - return services; - } - - @Override - public void close() throws Exception { - CloseableUtil.closeAll(services); - } - - @Override - public String toString() { - return "NetconfOperationServiceSnapshotImpl{" + netconfSessionIdForReporting + '}'; - } -} diff --git a/opendaylight/netconf/netconf-impl/src/main/yang/netconf-northbound-impl.yang b/opendaylight/netconf/netconf-impl/src/main/yang/netconf-northbound-impl.yang new file mode 100644 index 0000000000..7ad1fef55d --- /dev/null +++ b/opendaylight/netconf/netconf-impl/src/main/yang/netconf-northbound-impl.yang @@ -0,0 +1,128 @@ +// vi: set smarttab et sw=4 tabstop=4: +module netconf-northbound-impl { + + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:config:netconf:northbound:impl"; + prefix "cfg-net-s-i"; + + import config { prefix config; revision-date 2013-04-05; } + import netconf-northbound-mapper { prefix nnm; revision-date 2015-01-14; } + import netconf-northbound { prefix nn; revision-date 2015-01-14; } + import netty {prefix netty; } + + description + "This module contains the base YANG definitions for + netconf-server-dispatcher implementation. + + Copyright (c)2013 Cisco Systems, Inc. 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"; + + revision "2015-01-12" { + description + "Initial revision."; + } + + identity netconf-server-dispatcher-impl { + base config:module-type; + config:provided-service nn:netconf-server-dispatcher; + config:java-name-prefix NetconfServerDispatcher; + } + + augment "/config:modules/config:module/config:configuration" { + case netconf-server-dispatcher-impl { + when "/config:modules/config:module/config:type = 'netconf-server-dispatcher-impl'"; + + leaf connection-timeout-millis { + description "Specifies timeout in milliseconds after which connection must be established."; + type uint32; + default 20000; + } + + container boss-thread-group { + uses config:service-ref { + refine type { + config:required-identity netty:netty-threadgroup; + } + } + } + + container worker-thread-group { + uses config:service-ref { + refine type { + config:required-identity netty:netty-threadgroup; + } + } + } + + list mappers { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity nnm:netconf-northbound-mapper; + } + } + } + + container server-monitor { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity nn:netconf-server-monitoring; + } + } + } + + container timer { + uses config:service-ref { + refine type { + config:required-identity netty:netty-timer; + } + } + } + } + } + + + identity netconf-server-monitoring-impl { + base config:module-type; + config:provided-service nn:netconf-server-monitoring; + config:java-name-prefix NetconfServerMonitoring; + } + + // TODO Monitoring could expose the monitoring data over JMX... + + augment "/config:modules/config:module/config:configuration" { + case netconf-server-monitoring-impl { + when "/config:modules/config:module/config:type = 'netconf-server-monitoring-impl'"; + + container aggregator { + uses config:service-ref { + refine type { + config:required-identity nnm:netconf-northbound-mapper; + } + } + } + + } + } + + identity netconf-mapper-aggregator { + base config:module-type; + config:provided-service nnm:netconf-northbound-mapper; + config:provided-service nnm:netconf-mapper-registry; + config:java-name-prefix NetconfMapperAggregator; + description "Aggregated operation provider for netconf servers. Joins all the operations and capabilities of all the mappers it aggregates and exposes them as a single service. The dependency orientation is reversed in order to prevent cyclic dependencies when monitoring service is considered"; + } + + augment "/config:modules/config:module/config:configuration" { + case netconf-mapper-aggregator { + when "/config:modules/config:module/config:type = 'netconf-mapper-aggregator'"; + + } + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java index 3b83daa693..512a127d22 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/ConcurrentClientsTest.java @@ -12,7 +12,9 @@ import static com.google.common.base.Preconditions.checkNotNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anySetOf; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import com.google.common.base.Preconditions; @@ -48,8 +50,11 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.opendaylight.controller.netconf.api.Capability; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; @@ -57,9 +62,7 @@ import org.opendaylight.controller.netconf.client.SimpleNetconfClientSessionList import org.opendaylight.controller.netconf.client.TestingNetconfClient; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder; -import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl; -import org.opendaylight.controller.netconf.impl.osgi.SessionMonitoringService; -import org.opendaylight.controller.netconf.mapping.api.Capability; +import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory; import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; @@ -71,6 +74,8 @@ import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil; import org.opendaylight.controller.netconf.util.test.XmlFileLoader; import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.opendaylight.protocol.framework.NeverReconnectStrategy; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.CapabilitiesBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -115,10 +120,19 @@ public class ConcurrentClientsTest { HashedWheelTimer hashedWheelTimer; private TestingNetconfOperation testingNetconfOperation; - public static SessionMonitoringService createMockedMonitoringService() { - SessionMonitoringService monitoring = mock(SessionMonitoringService.class); + public static NetconfMonitoringService createMockedMonitoringService() { + NetconfMonitoringService monitoring = mock(NetconfMonitoringService.class); doNothing().when(monitoring).onSessionUp(any(NetconfServerSession.class)); doNothing().when(monitoring).onSessionDown(any(NetconfServerSession.class)); + doReturn(new AutoCloseable() { + @Override + public void close() throws Exception { + + } + }).when(monitoring).registerListener(any(NetconfMonitoringService.MonitoringListener.class)); + doNothing().when(monitoring).onCapabilitiesAdded(anySetOf(Capability.class)); + doNothing().when(monitoring).onCapabilitiesRemoved(anySetOf(Capability.class)); + doReturn(new CapabilitiesBuilder().setCapability(Collections.emptyList()).build()).when(monitoring).getCapabilities(); return monitoring; } @@ -143,7 +157,7 @@ public class ConcurrentClientsTest { nettyGroup = new NioEventLoopGroup(nettyThreads); netconfClientDispatcher = new NetconfClientDispatcherImpl(nettyGroup, nettyGroup, hashedWheelTimer); - NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); + AggregatedNetconfOperationServiceFactory factoriesListener = new AggregatedNetconfOperationServiceFactory(); testingNetconfOperation = new TestingNetconfOperation(); factoriesListener.onAddNetconfOperationServiceFactory(new TestingOperationServiceFactory(testingNetconfOperation)); @@ -155,8 +169,8 @@ public class ConcurrentClientsTest { commitNot = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer()); - NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer(serverNegotiatorFactory); - final NetconfServerDispatcher dispatch = new NetconfServerDispatcher(serverChannelInitializer, nettyGroup, nettyGroup); + NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer(serverNegotiatorFactory); + final NetconfServerDispatcherImpl dispatch = new NetconfServerDispatcherImpl(serverChannelInitializer, nettyGroup, nettyGroup); ChannelFuture s = dispatch.createServer(netconfAddress); s.await(); @@ -259,13 +273,22 @@ public class ConcurrentClientsTest { this.operations = operations; } + @Override + public Set getCapabilities() { + return Collections.emptySet(); + } + + @Override + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + return new AutoCloseable(){ + @Override + public void close() throws Exception {} + }; + } + @Override public NetconfOperationService createService(String netconfSessionIdForReporting) { return new NetconfOperationService() { - @Override - public Set getCapabilities() { - return Collections.emptySet(); - } @Override public Set getNetconfOperations() { diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfDispatcherImplTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfDispatcherImplTest.java index ecc33ca90f..4fd04e29ef 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfDispatcherImplTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfDispatcherImplTest.java @@ -17,12 +17,12 @@ import java.net.InetSocketAddress; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl; +import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory; public class NetconfDispatcherImplTest { private EventLoopGroup nettyGroup; - private NetconfServerDispatcher dispatch; + private NetconfServerDispatcherImpl dispatch; private DefaultCommitNotificationProducer commitNot; private HashedWheelTimer hashedWheelTimer; @@ -33,16 +33,16 @@ public class NetconfDispatcherImplTest { commitNot = new DefaultCommitNotificationProducer( ManagementFactory.getPlatformMBeanServer()); - NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); + AggregatedNetconfOperationServiceFactory factoriesListener = new AggregatedNetconfOperationServiceFactory(); SessionIdProvider idProvider = new SessionIdProvider(); hashedWheelTimer = new HashedWheelTimer(); NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory( hashedWheelTimer, factoriesListener, idProvider, 5000, commitNot, ConcurrentClientsTest.createMockedMonitoringService()); - NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer(serverNegotiatorFactory); + NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer(serverNegotiatorFactory); - dispatch = new NetconfServerDispatcher( + dispatch = new NetconfServerDispatcherImpl( serverChannelInitializer, nettyGroup, nettyGroup); } diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfMonitoringServiceImplTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfMonitoringServiceImplTest.java index 93caa09286..fed4742dd8 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfMonitoringServiceImplTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/NetconfMonitoringServiceImplTest.java @@ -8,107 +8,7 @@ package org.opendaylight.controller.netconf.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import com.google.common.base.Optional; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import io.netty.channel.Channel; -import java.util.Set; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession; -import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl; -import org.opendaylight.controller.netconf.mapping.api.Capability; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot; -import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader; - public class NetconfMonitoringServiceImplTest { - private NetconfMonitoringServiceImpl service; - - @Mock - private NetconfOperationProvider operationProvider; - @Mock - private NetconfManagementSession managementSession; - @Mock - private NetconfOperationServiceSnapshot snapshot; - @Mock - private NetconfOperationService operationService; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - service = new NetconfMonitoringServiceImpl(operationProvider); - } - - @Test - public void testSessions() throws Exception { - doReturn("sessToStr").when(managementSession).toString(); - service.onSessionUp(managementSession); - } - - @Test(expected = RuntimeException.class) - public void testGetSchemas() throws Exception { - doThrow(RuntimeException.class).when(operationProvider).openSnapshot(anyString()); - service.getSchemas(); - } - - @Test(expected = IllegalStateException.class) - public void testGetSchemas2() throws Exception { - doThrow(Exception.class).when(operationProvider).openSnapshot(anyString()); - service.getSchemas(); - } - - @Test - public void testGetSchemas3() throws Exception { - doReturn("").when(managementSession).toString(); - Capability cap = mock(Capability.class); - Set caps = Sets.newHashSet(cap); - Set services = Sets.newHashSet(operationService); - doReturn(snapshot).when(operationProvider).openSnapshot(anyString()); - doReturn(services).when(snapshot).getServices(); - doReturn(caps).when(operationService).getCapabilities(); - Optional opt = mock(Optional.class); - doReturn(opt).when(cap).getCapabilitySchema(); - doReturn(true).when(opt).isPresent(); - doReturn(opt).when(cap).getModuleNamespace(); - doReturn("namespace").when(opt).get(); - Optional optRev = Optional.of("rev"); - doReturn(optRev).when(cap).getRevision(); - doReturn(Optional.of("modName")).when(cap).getModuleName(); - doReturn(Lists.newArrayList("loc")).when(cap).getLocation(); - doNothing().when(snapshot).close(); - - assertNotNull(service.getSchemas()); - verify(snapshot, times(1)).close(); - - NetconfServerSessionListener sessionListener = mock(NetconfServerSessionListener.class); - Channel channel = mock(Channel.class); - doReturn("mockChannel").when(channel).toString(); - NetconfHelloMessageAdditionalHeader header = new NetconfHelloMessageAdditionalHeader("name", "addr", "2", "tcp", "id"); - NetconfServerSession sm = new NetconfServerSession(sessionListener, channel, 10, header); - doNothing().when(sessionListener).onSessionUp(any(NetconfServerSession.class)); - sm.sessionUp(); - service.onSessionUp(sm); - assertEquals(1, service.getSessions().getSession().size()); - - assertEquals(Long.valueOf(10), service.getSessions().getSession().get(0).getSessionId()); - - service.onSessionDown(sm); - assertEquals(0, service.getSessions().getSession().size()); - } + // TODO redo test } diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommitTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommitTest.java index 15aeb8d27c..0c7406543f 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommitTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultCommitTest.java @@ -18,17 +18,19 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import com.google.common.collect.Sets; +import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; import org.opendaylight.controller.netconf.impl.NetconfServerSession; -import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationRouter; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; import org.opendaylight.controller.netconf.util.test.XmlFileLoader; import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.CapabilitiesBuilder; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -38,7 +40,7 @@ public class DefaultCommitTest { private Document requestMessage; private NetconfOperationRouter router; private DefaultCommitNotificationProducer notifier; - private CapabilityProvider cap; + private NetconfMonitoringService cap; private DefaultCommit commit; @Before @@ -49,8 +51,8 @@ public class DefaultCommitTest { doReturn(false).when(operation).isExecutionTermination(); notifier = mock(DefaultCommitNotificationProducer.class); doNothing().when(notifier).sendCommitNotification(anyString(), any(Element.class), anySetOf(String.class)); - cap = mock(CapabilityProvider.class); - doReturn(Sets.newHashSet()).when(cap).getCapabilities(); + cap = mock(NetconfMonitoringService.class); + doReturn(new CapabilitiesBuilder().setCapability(Collections.emptyList()).build()).when(cap).getCapabilities(); Document rpcData = XmlFileLoader.xmlFileToDocument("netconfMessages/editConfig_expectedResult.xml"); doReturn(rpcData).when(router).onNetconfMessage(any(Document.class), any(NetconfServerSession.class)); commit = new DefaultCommit(notifier, cap, "", router); diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivatorTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivatorTest.java index 413c9cc945..2fb989507c 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivatorTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/osgi/NetconfImplActivatorTest.java @@ -12,7 +12,6 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.util.Arrays; @@ -44,11 +43,11 @@ public class NetconfImplActivatorTest { doReturn(filter).when(bundle).createFilter(anyString()); doNothing().when(bundle).addServiceListener(any(ServiceListener.class), anyString()); - ServiceReference[] refs = new ServiceReference[0]; + ServiceReference[] refs = {}; doReturn(refs).when(bundle).getServiceReferences(anyString(), anyString()); doReturn(Arrays.asList(refs)).when(bundle).getServiceReferences(any(Class.class), anyString()); doReturn("").when(bundle).getProperty(anyString()); - doReturn(registration).when(bundle).registerService(any(Class.class), any(NetconfOperationServiceFactoryListenerImpl.class), any(Dictionary.class)); + doReturn(registration).when(bundle).registerService(any(Class.class), any(AggregatedNetconfOperationServiceFactory.class), any(Dictionary.class)); doNothing().when(registration).unregister(); doNothing().when(bundle).removeServiceListener(any(ServiceListener.class)); } @@ -57,7 +56,7 @@ public class NetconfImplActivatorTest { public void testStart() throws Exception { NetconfImplActivator activator = new NetconfImplActivator(); activator.start(bundle); - verify(bundle, times(2)).registerService(any(Class.class), any(NetconfOperationServiceFactoryListenerImpl.class), any(Dictionary.class)); + verify(bundle).registerService(any(Class.class), any(AggregatedNetconfOperationServiceFactory.class), any(Dictionary.class)); activator.stop(bundle); } } diff --git a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTrackerTest.java b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTrackerTest.java index d744504bb2..c75cdcd27a 100644 --- a/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTrackerTest.java +++ b/opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryTrackerTest.java @@ -20,7 +20,9 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.netconf.api.util.NetconfConstants; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactoryListener; import org.osgi.framework.BundleContext; import org.osgi.framework.Filter; import org.osgi.framework.ServiceReference; @@ -46,6 +48,7 @@ public class NetconfOperationServiceFactoryTrackerTest { doNothing().when(listener).onRemoveNetconfOperationServiceFactory(any(NetconfOperationServiceFactory.class)); doReturn(filter).when(context).createFilter(anyString()); doReturn("").when(reference).toString(); + doReturn(NetconfConstants.CONFIG_NETCONF_CONNECTOR).when(reference).getProperty(NetconfConstants.SERVICE_NAME); doReturn(factory).when(context).getService(any(ServiceReference.class)); doReturn("").when(factory).toString(); doNothing().when(listener).onAddNetconfOperationServiceFactory(any(NetconfOperationServiceFactory.class)); diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/AbstractNetconfConfigTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/AbstractNetconfConfigTest.java index fd362f83e7..c2812dbf61 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/AbstractNetconfConfigTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/AbstractNetconfConfigTest.java @@ -13,7 +13,6 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anySetOf; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import io.netty.channel.Channel; @@ -47,28 +46,26 @@ import org.opendaylight.controller.config.yang.test.impl.MultipleDependenciesMod import org.opendaylight.controller.config.yang.test.impl.NetconfTestImplModuleFactory; import org.opendaylight.controller.config.yang.test.impl.TestImplModuleFactory; import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; import org.opendaylight.controller.netconf.client.SimpleNetconfClientSessionListener; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder; import org.opendaylight.controller.netconf.confignetconfconnector.osgi.NetconfOperationServiceFactoryImpl; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreException; import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreService; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreServiceImpl; -import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStoreSnapshot; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; -import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher; +import org.opendaylight.controller.netconf.impl.NetconfServerDispatcherImpl; import org.opendaylight.controller.netconf.impl.NetconfServerSessionNegotiatorFactory; import org.opendaylight.controller.netconf.impl.SessionIdProvider; +import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory; import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl; -import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl; -import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceSnapshotImpl; -import org.opendaylight.controller.netconf.impl.osgi.SessionMonitoringService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringActivator; +import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringOperationService; +import org.opendaylight.controller.netconf.notifications.BaseNetconfNotificationListener; import org.opendaylight.controller.netconf.util.test.XmlFileLoader; import org.opendaylight.protocol.framework.NeverReconnectStrategy; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaContextProvider; import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; @@ -109,14 +106,16 @@ public abstract class AbstractNetconfConfigTest extends AbstractConfigTest { setUpTestInitial(); - final NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl(); + final AggregatedNetconfOperationServiceFactory factoriesListener = new AggregatedNetconfOperationServiceFactory(); + final NetconfMonitoringService netconfMonitoringService = getNetconfMonitoringService(factoriesListener); factoriesListener.onAddNetconfOperationServiceFactory(new NetconfOperationServiceFactoryImpl(getYangStore())); + factoriesListener.onAddNetconfOperationServiceFactory(new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory(new NetconfMonitoringOperationService(netconfMonitoringService))); - for (final NetconfOperationServiceFactory netconfOperationServiceFactory : getAdditionalServiceFactories()) { + for (final NetconfOperationServiceFactory netconfOperationServiceFactory : getAdditionalServiceFactories(factoriesListener)) { factoriesListener.onAddNetconfOperationServiceFactory(netconfOperationServiceFactory); } - serverTcpChannel = startNetconfTcpServer(factoriesListener); + serverTcpChannel = startNetconfTcpServer(factoriesListener, netconfMonitoringService); clientDispatcher = new NetconfClientDispatcherImpl(getNettyThreadgroup(), getNettyThreadgroup(), getHashedWheelTimer()); } @@ -138,8 +137,8 @@ public abstract class AbstractNetconfConfigTest extends AbstractConfigTest { return get; } - private Channel startNetconfTcpServer(final NetconfOperationServiceFactoryListenerImpl factoriesListener) throws Exception { - final NetconfServerDispatcher dispatch = createDispatcher(factoriesListener, getNetconfMonitoringService(), getNotificationProducer()); + private Channel startNetconfTcpServer(final AggregatedNetconfOperationServiceFactory listener, final NetconfMonitoringService monitoring) throws Exception { + final NetconfServerDispatcherImpl dispatch = createDispatcher(listener, monitoring, getNotificationProducer()); final ChannelFuture s; if(getTcpServerAddress() instanceof LocalAddress) { @@ -158,16 +157,12 @@ public abstract class AbstractNetconfConfigTest extends AbstractConfigTest { return notificationProducer; } - protected Iterable getAdditionalServiceFactories() { + protected Iterable getAdditionalServiceFactories(final AggregatedNetconfOperationServiceFactory factoriesListener) throws Exception { return Collections.emptySet(); } - protected SessionMonitoringService getNetconfMonitoringService() throws Exception { - final NetconfOperationProvider netconfOperationProvider = mock(NetconfOperationProvider.class); - final NetconfOperationServiceSnapshotImpl snap = mock(NetconfOperationServiceSnapshotImpl.class); - doReturn(Collections.emptySet()).when(snap).getServices(); - doReturn(snap).when(netconfOperationProvider).openSnapshot(anyString()); - return new NetconfMonitoringServiceImpl(netconfOperationProvider); + protected NetconfMonitoringService getNetconfMonitoringService(final AggregatedNetconfOperationServiceFactory factoriesListener) throws Exception { + return new NetconfMonitoringServiceImpl(factoriesListener); } protected abstract SocketAddress getTcpServerAddress(); @@ -176,7 +171,7 @@ public abstract class AbstractNetconfConfigTest extends AbstractConfigTest { return clientDispatcher; } - private HardcodedYangStoreService getYangStore() throws YangStoreException, IOException { + private HardcodedYangStoreService getYangStore() throws IOException { final Collection yangDependencies = getBasicYangs(); return new HardcodedYangStoreService(yangDependencies); } @@ -206,17 +201,17 @@ public abstract class AbstractNetconfConfigTest extends AbstractConfigTest { return yangDependencies; } - protected NetconfServerDispatcher createDispatcher( - final NetconfOperationServiceFactoryListenerImpl factoriesListener, final SessionMonitoringService sessionMonitoringService, + protected NetconfServerDispatcherImpl createDispatcher( + final AggregatedNetconfOperationServiceFactory factoriesListener, final NetconfMonitoringService sessionMonitoringService, final DefaultCommitNotificationProducer commitNotifier) { final SessionIdProvider idProvider = new SessionIdProvider(); final NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory( hashedWheelTimer, factoriesListener, idProvider, SERVER_CONNECTION_TIMEOUT_MILLIS, commitNotifier, sessionMonitoringService); - final NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer( + final NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer( serverNegotiatorFactory); - return new NetconfServerDispatcher(serverChannelInitializer, nettyThreadgroup, nettyThreadgroup); + return new NetconfServerDispatcherImpl(serverChannelInitializer, nettyThreadgroup, nettyThreadgroup); } protected HashedWheelTimer getHashedWheelTimer() { @@ -246,22 +241,35 @@ public abstract class AbstractNetconfConfigTest extends AbstractConfigTest { return b.build(); } - public static final class HardcodedYangStoreService implements YangStoreService { - - private final List byteArrayInputStreams; + public static final class HardcodedYangStoreService extends YangStoreService { + public HardcodedYangStoreService(final Collection inputStreams) throws IOException { + super(new SchemaContextProvider() { + @Override + public SchemaContext getSchemaContext() { + return getSchema(inputStreams); + } + }, new BaseNetconfNotificationListener() { + @Override + public void onCapabilityChanged(final NetconfCapabilityChange capabilityChange) { + // NOOP + } + }); + } - public HardcodedYangStoreService(final Collection inputStreams) throws YangStoreException, IOException { - byteArrayInputStreams = new ArrayList<>(); + private static SchemaContext getSchema(final Collection inputStreams) { + final ArrayList byteArrayInputStreams = new ArrayList<>(); for (final InputStream inputStream : inputStreams) { assertNotNull(inputStream); - final byte[] content = IOUtils.toByteArray(inputStream); + final byte[] content; + try { + content = IOUtils.toByteArray(inputStream); + } catch (IOException e) { + throw new IllegalStateException("Cannot read " + inputStream, e); + } final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(content); byteArrayInputStreams.add(byteArrayInputStream); } - } - @Override - public YangStoreSnapshot getYangStoreSnapshot() throws YangStoreException { for (final InputStream inputStream : byteArrayInputStreams) { try { inputStream.reset(); @@ -271,14 +279,7 @@ public abstract class AbstractNetconfConfigTest extends AbstractConfigTest { } final YangParserImpl yangParser = new YangParserImpl(); - final SchemaContext schemaContext = yangParser.resolveSchemaContext(new HashSet<>(yangParser.parseYangModelsFromStreamsMapped(byteArrayInputStreams).values())); - final YangStoreServiceImpl yangStoreService = new YangStoreServiceImpl(new SchemaContextProvider() { - @Override - public SchemaContext getSchemaContext() { - return schemaContext ; - } - }); - return yangStoreService.getYangStoreSnapshot(); + return yangParser.resolveSchemaContext(new HashSet<>(yangParser.parseYangModelsFromStreamsMapped(byteArrayInputStreams).values())); } } } diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfConfigPersisterITTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfConfigPersisterITTest.java index 92c96d92f2..c421a46fdf 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfConfigPersisterITTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfConfigPersisterITTest.java @@ -9,23 +9,18 @@ package org.opendaylight.controller.netconf.it; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.opendaylight.controller.netconf.util.test.XmlUnitUtil.assertContainsElementWithName; import static org.opendaylight.controller.netconf.util.test.XmlUnitUtil.assertElementsCount; import static org.opendaylight.controller.netconf.util.xml.XmlUtil.readXmlToDocument; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.Collections; import java.util.List; -import java.util.Set; import javax.management.InstanceNotFoundException; import javax.management.Notification; import javax.management.NotificationListener; @@ -38,15 +33,6 @@ import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.jmx.CommitJMXNotification; import org.opendaylight.controller.netconf.client.TestingNetconfClient; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; -import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl; -import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceSnapshotImpl; -import org.opendaylight.controller.netconf.impl.osgi.SessionMonitoringService; -import org.opendaylight.controller.netconf.mapping.api.Capability; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; -import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringActivator; -import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringOperationService; import org.opendaylight.controller.netconf.persist.impl.ConfigPersisterNotificationHandler; import org.opendaylight.controller.netconf.util.test.XmlFileLoader; import org.w3c.dom.Document; @@ -58,29 +44,12 @@ public class NetconfConfigPersisterITTest extends AbstractNetconfConfigTest { public static final int PORT = 12026; private static final InetSocketAddress TCP_ADDRESS = new InetSocketAddress(LOOPBACK_ADDRESS, PORT); - private NetconfMonitoringServiceImpl netconfMonitoringService; - - @Override - protected void setUpTestInitial() { - netconfMonitoringService = new NetconfMonitoringServiceImpl(getNetconfOperationProvider()); - } - - @Override - protected SessionMonitoringService getNetconfMonitoringService() throws Exception { - return netconfMonitoringService; - } @Override protected SocketAddress getTcpServerAddress() { return TCP_ADDRESS; } - @Override - protected Iterable getAdditionalServiceFactories() { - return Collections.singletonList(new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory( - new NetconfMonitoringOperationService(netconfMonitoringService))); - } - @Override protected DefaultCommitNotificationProducer getNotificationProducer() { return new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer()); @@ -95,7 +64,6 @@ public class NetconfConfigPersisterITTest extends AbstractNetconfConfigTest { try (ConfigPersisterNotificationHandler configPersisterNotificationHandler = new ConfigPersisterNotificationHandler( platformMBeanServer, mockedAggregator)) { - try (TestingNetconfClient netconfClient = new TestingNetconfClient("client", getClientDispatcher(), getClientConfiguration(TCP_ADDRESS, 4000))) { NetconfMessage response = netconfClient.sendMessage(loadGetConfigMessage()); assertContainsElementWithName(response.getDocument(), "modules"); @@ -112,8 +80,8 @@ public class NetconfConfigPersisterITTest extends AbstractNetconfConfigTest { } notificationVerifier.assertNotificationCount(2); - notificationVerifier.assertNotificationContent(0, 0, 0, 9); - notificationVerifier.assertNotificationContent(1, 4, 3, 9); + notificationVerifier.assertNotificationContent(0, 0, 0, 8); + notificationVerifier.assertNotificationContent(1, 4, 3, 8); mockedAggregator.assertSnapshotCount(2); // Capabilities are stripped for persister @@ -143,20 +111,6 @@ public class NetconfConfigPersisterITTest extends AbstractNetconfConfigTest { return XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/commit.xml"); } - - public NetconfOperationProvider getNetconfOperationProvider() { - final NetconfOperationProvider factoriesListener = mock(NetconfOperationProvider.class); - final NetconfOperationServiceSnapshotImpl snap = mock(NetconfOperationServiceSnapshotImpl.class); - final NetconfOperationService service = mock(NetconfOperationService.class); - final Set caps = Sets.newHashSet(); - doReturn(caps).when(service).getCapabilities(); - final Set services = Sets.newHashSet(service); - doReturn(services).when(snap).getServices(); - doReturn(snap).when(factoriesListener).openSnapshot(anyString()); - - return factoriesListener; - } - private static class VerifyingNotificationListener implements NotificationListener { public List notifications = Lists.newArrayList(); diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITMonitoringTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITMonitoringTest.java index ea94fd3f8b..e745b393fb 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITMonitoringTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITMonitoringTest.java @@ -8,6 +8,7 @@ package org.opendaylight.controller.netconf.it; import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -26,21 +27,14 @@ import java.util.Collections; import java.util.List; import java.util.Set; import org.junit.Test; +import org.opendaylight.controller.netconf.api.Capability; import org.opendaylight.controller.netconf.api.NetconfMessage; -import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; import org.opendaylight.controller.netconf.client.TestingNetconfClient; -import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl; -import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceSnapshotImpl; -import org.opendaylight.controller.netconf.impl.osgi.SessionMonitoringService; -import org.opendaylight.controller.netconf.mapping.api.Capability; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; +import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; -import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringActivator; -import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringOperationService; import org.opendaylight.controller.netconf.util.test.XmlFileLoader; import org.opendaylight.controller.netconf.util.xml.XmlUtil; -import org.slf4j.Logger; import org.w3c.dom.Document; public class NetconfITMonitoringTest extends AbstractNetconfConfigTest { @@ -49,45 +43,11 @@ public class NetconfITMonitoringTest extends AbstractNetconfConfigTest { public static final InetSocketAddress TCP_ADDRESS = new InetSocketAddress(LOOPBACK_ADDRESS, PORT); public static final TestingCapability TESTING_CAPABILITY = new TestingCapability(); - private NetconfMonitoringServiceImpl netconfMonitoringService; - - @Override - protected void setUpTestInitial() { - netconfMonitoringService = new NetconfMonitoringServiceImpl(getNetconfOperationProvider()); - } - - @Override - protected SessionMonitoringService getNetconfMonitoringService() throws Exception { - return netconfMonitoringService; - } - - @Override - protected Iterable getAdditionalServiceFactories() { - return Collections.singletonList(new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory( - new NetconfMonitoringOperationService(netconfMonitoringService))); - } - @Override protected InetSocketAddress getTcpServerAddress() { return TCP_ADDRESS; } - static SessionMonitoringService getNetconfMonitoringListenerService(final Logger LOG, final NetconfMonitoringServiceImpl monitor) { - return new SessionMonitoringService() { - @Override - public void onSessionUp(final NetconfManagementSession session) { - LOG.debug("Management session up {}", session); - monitor.onSessionUp(session); - } - - @Override - public void onSessionDown(final NetconfManagementSession session) { - LOG.debug("Management session down {}", session); - monitor.onSessionDown(session); - } - }; - } - @Test public void testGetResponseFromMonitoring() throws Exception { try (TestingNetconfClient netconfClient = new TestingNetconfClient("client-monitoring", getClientDispatcher(), getClientConfiguration(TCP_ADDRESS, 10000))) { @@ -151,23 +111,24 @@ public class NetconfITMonitoringTest extends AbstractNetconfConfigTest { assertEquals("Incorrect number of session-id tags in " + XmlUtil.toString(document), i, elementSize); } - public static NetconfOperationProvider getNetconfOperationProvider() { - final NetconfOperationProvider factoriesListener = mock(NetconfOperationProvider.class); - final NetconfOperationServiceSnapshotImpl snap = mock(NetconfOperationServiceSnapshotImpl.class); + public static AggregatedNetconfOperationServiceFactory getNetconfOperationProvider() throws Exception { + final AggregatedNetconfOperationServiceFactory factoriesListener = mock(AggregatedNetconfOperationServiceFactory.class); + final NetconfOperationService snap = mock(NetconfOperationService.class); try { doNothing().when(snap).close(); } catch (final Exception e) { // not happening throw new IllegalStateException(e); } - final NetconfOperationService service = mock(NetconfOperationService.class); final Set caps = Sets.newHashSet(); caps.add(TESTING_CAPABILITY); - doReturn(caps).when(service).getCapabilities(); - final Set services = Sets.newHashSet(service); - doReturn(services).when(snap).getServices(); - doReturn(snap).when(factoriesListener).openSnapshot(anyString()); + doReturn(caps).when(factoriesListener).getCapabilities(); + doReturn(snap).when(factoriesListener).createService(anyString()); + + AutoCloseable mock = mock(AutoCloseable.class); + doNothing().when(mock).close(); + doReturn(mock).when(factoriesListener).registerCapabilityListener(any(CapabilityListener.class)); return factoriesListener; } diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java index 7d568b6462..029aefff6e 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java @@ -58,9 +58,8 @@ import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.opendaylight.controller.sal.connect.api.RemoteDevice; -import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; -import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.protocol.framework.NeverReconnectStrategy; import org.opendaylight.yangtools.yang.common.QName; @@ -199,8 +198,8 @@ public class NetconfITSecureTest extends AbstractNetconfConfigTest { } static NetconfDeviceCommunicator getSessionListener() { - RemoteDevice mockedRemoteDevice = mock(RemoteDevice.class); - doNothing().when(mockedRemoteDevice).onRemoteSessionUp(any(NetconfSessionCapabilities.class), any(RemoteDeviceCommunicator.class)); + RemoteDevice mockedRemoteDevice = mock(RemoteDevice.class); + doNothing().when(mockedRemoteDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class), any(NetconfDeviceCommunicator.class)); doNothing().when(mockedRemoteDevice).onRemoteSessionDown(); return new NetconfDeviceCommunicator(new RemoteDeviceId("secure-test"), mockedRemoteDevice); } diff --git a/opendaylight/netconf/netconf-mapping-api/pom.xml b/opendaylight/netconf/netconf-mapping-api/pom.xml index f5ae0ec610..21e50cd2ea 100644 --- a/opendaylight/netconf/netconf-mapping-api/pom.xml +++ b/opendaylight/netconf/netconf-mapping-api/pom.xml @@ -29,12 +29,42 @@ org.apache.felix maven-bundle-plugin - - - org.opendaylight.controller.netconf.mapping.api, - - + + org.opendaylight.yangtools + yang-maven-plugin + + + config + + generate-sources + + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + ${jmxGeneratorPath} + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + ${salGeneratorPath} + + + true + + + + + + org.opendaylight.controller + yang-jmx-generator-plugin + ${config.version} + + + diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationProvider.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationProvider.java deleted file mode 100644 index f5c50f8167..0000000000 --- a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationProvider.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.mapping.api; - -public interface NetconfOperationProvider { - - NetconfOperationServiceSnapshot openSnapshot(String sessionIdForReporting); - - public static class NetconfOperationProviderUtil { - - public static String getNetconfSessionIdForReporting(long sessionId) { - return "netconf session id " + sessionId; - } - - } - -} diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationService.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationService.java index 5e2cba2cba..1a1a124895 100644 --- a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationService.java +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationService.java @@ -15,11 +15,6 @@ import java.util.Set; */ public interface NetconfOperationService extends AutoCloseable { - /** - * Get capabilities announced by server hello message. - */ - Set getCapabilities(); - /** * Get set of netconf operations that are handled by this service. */ diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactory.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactory.java index 81401f26ee..8caa177bfa 100644 --- a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactory.java +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactory.java @@ -8,6 +8,10 @@ package org.opendaylight.controller.netconf.mapping.api; +import java.util.Set; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; + /** * Factory that must be registered in OSGi service registry in order to be used * by netconf-impl. Responsible for creating per-session instances of @@ -15,6 +19,16 @@ package org.opendaylight.controller.netconf.mapping.api; */ public interface NetconfOperationServiceFactory { + /** + * Get capabilities supported by current operation service. + */ + Set getCapabilities(); + + /** + * Supported capabilities may change over time, registering a listener allows for push based information retrieval about current notifications + */ + AutoCloseable registerCapabilityListener(CapabilityListener listener); + NetconfOperationService createService(String netconfSessionIdForReporting); } diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListener.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactoryListener.java similarity index 79% rename from opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListener.java rename to opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactoryListener.java index 8e1052cfeb..30a4f9bf82 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/osgi/NetconfOperationServiceFactoryListener.java +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceFactoryListener.java @@ -6,9 +6,7 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.impl.osgi; - -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +package org.opendaylight.controller.netconf.mapping.api; public interface NetconfOperationServiceFactoryListener { diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceSnapshot.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/SessionAwareNetconfOperation.java similarity index 54% rename from opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceSnapshot.java rename to opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/SessionAwareNetconfOperation.java index eaa69379f7..88c77c6666 100644 --- a/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/NetconfOperationServiceSnapshot.java +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/SessionAwareNetconfOperation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, @@ -8,11 +8,9 @@ package org.opendaylight.controller.netconf.mapping.api; -import java.util.Set; +import org.opendaylight.controller.netconf.api.NetconfSession; -public interface NetconfOperationServiceSnapshot extends AutoCloseable { - String getNetconfSessionIdForReporting(); - - Set getServices(); +public interface SessionAwareNetconfOperation extends NetconfOperation { + void setSession(NetconfSession session); } diff --git a/opendaylight/netconf/netconf-mapping-api/src/main/yang/netconf-northbound-mapper.yang b/opendaylight/netconf/netconf-mapping-api/src/main/yang/netconf-northbound-mapper.yang new file mode 100644 index 0000000000..3ffecde700 --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/yang/netconf-northbound-mapper.yang @@ -0,0 +1,27 @@ +module netconf-northbound-mapper { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:netconf:north:mapper"; + prefix "nnm"; + + import config { prefix config; revision-date 2013-04-05; } + + description + "This module contains the base YANG definitions for + mapping services plugged into a netconf northbound server"; + + revision "2015-01-14" { + description + "Initial revision."; + } + + identity netconf-northbound-mapper { + base "config:service-type"; + config:java-class "org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory"; + } + + identity netconf-mapper-registry { + base "config:service-type"; + config:java-class "org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactoryListener"; + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-mdsal-config/pom.xml b/opendaylight/netconf/netconf-mdsal-config/pom.xml new file mode 100644 index 0000000000..f3c931345f --- /dev/null +++ b/opendaylight/netconf/netconf-mdsal-config/pom.xml @@ -0,0 +1,46 @@ + + + + + 4.0.0 + + org.opendaylight.controller + netconf-subsystem + 0.3.0-SNAPSHOT + + netconf-mdsal-config + Configuration files for netconf for mdsal + jar + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-artifacts + + attach-artifact + + package + + + + ${project.build.directory}/classes/initial/08-netconf-mdsal.xml + xml + config + + + + + + + + + diff --git a/opendaylight/netconf/netconf-mdsal-config/src/main/resources/initial/08-netconf-mdsal.xml b/opendaylight/netconf/netconf-mdsal-config/src/main/resources/initial/08-netconf-mdsal.xml new file mode 100644 index 0000000000..4ca3c99e81 --- /dev/null +++ b/opendaylight/netconf/netconf-mdsal-config/src/main/resources/initial/08-netconf-mdsal.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + prefix:netconf-mdsal-mapper + netconf-mdsal-mapper + + dom:schema-service + yang-schema-service + + + dom:dom-async-data-broker + inmemory-data-broker + + + prefix:netconf-mapper-registry + mapper-aggregator-registry + + + + + prefix:netconf-server-dispatcher-impl + netconf-mdsal-server-dispatcher + + dom:netconf-northbound-mapper + mapper-aggregator + + + prefix:netconf-server-monitoring + server-monitor + + + prefix:netty-threadgroup + global-boss-group + + + prefix:netty-threadgroup + global-worker-group + + + prefix:netty-timer + global-timer + + + + + prefix:netconf-mdsal-monitoring-mapper + netconf-mdsal-monitoring-mapper + + prefix:netconf-server-monitoring + server-monitor + + + prefix:binding-broker-osgi-registry + binding-osgi-broker + + + prefix:netconf-mapper-registry + mapper-aggregator-registry + + + + + prefix:netconf-mapper-aggregator + mapper-aggregator + + + + prefix:netconf-server-monitoring-impl + server-monitor + + dom:netconf-northbound-mapper + mapper-aggregator + + + + + prefix:netconf-northbound-ssh + netconf-mdsal-ssh-server + + + prefix:netty-event-executor + global-event-executor + + + prefix:netty-threadgroup + global-worker-group + + + prefix:threadpool + global-netconf-ssh-scheduled-executor + + + prefix:netconf-server-dispatcher + netconf-mdsal-server-dispatcher + + + admin + admin + + + + + + + prefix:netconf-server-monitoring + + server-monitor + /modules/module[type='netconf-server-monitoring-impl'][name='server-monitor'] + + + + prefix:netconf-northbound-mapper + + netconf-mdsal-mapper + /modules/module[type='netconf-mdsal-mapper'][name='netconf-mdsal-mapper'] + + + + prefix:netconf-northbound-mapper + + mapper-aggregator + /modules/module[type='netconf-mapper-aggregator'][name='mapper-aggregator'] + + + + prefix:netconf-mapper-registry + + mapper-aggregator-registry + /modules/module[type='netconf-mapper-aggregator'][name='mapper-aggregator'] + + + + prefix:netconf-server-dispatcher + + netconf-mdsal-server-dispatcher + /modules/module[type='netconf-server-dispatcher-impl'][name='netconf-mdsal-server-dispatcher'] + + + + + + + + urn:opendaylight:params:xml:ns:yang:controller:netconf:mdsal:mapper?module=netconf-mdsal-mapper&revision=2015-01-14 + urn:opendaylight:params:xml:ns:yang:controller:netconf:mdsal:monitoring?module=netconf-mdsal-monitoring&revision=2015-02-18 + urn:opendaylight:params:xml:ns:yang:controller:netconf:northbound:ssh?module=netconf-northbound-ssh&revision=2015-01-14 + urn:opendaylight:params:xml:ns:yang:controller:config:netconf:northbound:impl?module=netconf-northbound-impl&revision=2015-01-12 + urn:opendaylight:params:xml:ns:yang:controller:threadpool:impl:scheduled?module=threadpool-impl-scheduled&revision=2013-12-01 + + diff --git a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultGetSchema.java b/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/GetSchema.java similarity index 75% rename from opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultGetSchema.java rename to opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/GetSchema.java index 5310704876..d02cb432cb 100644 --- a/opendaylight/netconf/netconf-impl/src/main/java/org/opendaylight/controller/netconf/impl/mapping/operations/DefaultGetSchema.java +++ b/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/GetSchema.java @@ -1,19 +1,19 @@ /* - * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.impl.mapping.operations; +package org.opendaylight.controller.netconf.monitoring; import com.google.common.base.Optional; import com.google.common.collect.Maps; import java.util.Map; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; -import org.opendaylight.controller.netconf.impl.mapping.CapabilityProvider; import org.opendaylight.controller.netconf.util.exception.MissingNameSpaceException; import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; import org.opendaylight.controller.netconf.util.xml.XmlElement; @@ -23,16 +23,16 @@ import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; -public final class DefaultGetSchema extends AbstractLastNetconfOperation { +public class GetSchema extends AbstractLastNetconfOperation { public static final String GET_SCHEMA = "get-schema"; public static final String IDENTIFIER = "identifier"; public static final String VERSION = "version"; - private static final Logger LOG = LoggerFactory.getLogger(DefaultGetSchema.class); - private final CapabilityProvider cap; + private static final Logger LOG = LoggerFactory.getLogger(GetSchema.class); + private final NetconfMonitoringService cap; - public DefaultGetSchema(CapabilityProvider cap, String netconfSessionIdForReporting) { - super(netconfSessionIdForReporting); + public GetSchema(final NetconfMonitoringService cap) { + super(MonitoringConstants.MODULE_NAME); this.cap = cap; } @@ -47,16 +47,16 @@ public final class DefaultGetSchema extends AbstractLastNetconfOperation { } @Override - protected Element handleWithNoSubsequentOperations(Document document, XmlElement xml) throws NetconfDocumentedException { - GetSchemaEntry entry; + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement xml) throws NetconfDocumentedException { + final GetSchemaEntry entry; entry = new GetSchemaEntry(xml); - String schema; + final String schema; try { schema = cap.getSchemaForCapability(entry.identifier, entry.version); - } catch (IllegalStateException e) { - Map errorInfo = Maps.newHashMap(); + } catch (final IllegalStateException e) { + final Map errorInfo = Maps.newHashMap(); errorInfo.put(entry.identifier, e.getMessage()); LOG.warn("Rpc error: {}", NetconfDocumentedException.ErrorTag.operation_failed, e); throw new NetconfDocumentedException(e.getMessage(), NetconfDocumentedException.ErrorType.application, @@ -64,7 +64,7 @@ public final class DefaultGetSchema extends AbstractLastNetconfOperation { NetconfDocumentedException.ErrorSeverity.error, errorInfo); } - Element getSchemaResult; + final Element getSchemaResult; getSchemaResult = XmlUtil.createTextElement(document, XmlNetconfConstants.DATA_KEY, schema, Optional.of(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_YANG_IETF_NETCONF_MONITORING)); LOG.trace("{} operation successful", GET_SCHEMA); @@ -76,19 +76,19 @@ public final class DefaultGetSchema extends AbstractLastNetconfOperation { private final String identifier; private final Optional version; - GetSchemaEntry(XmlElement getSchemaElement) throws NetconfDocumentedException { + GetSchemaEntry(final XmlElement getSchemaElement) throws NetconfDocumentedException { getSchemaElement.checkName(GET_SCHEMA); getSchemaElement.checkNamespace(XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_YANG_IETF_NETCONF_MONITORING); XmlElement identifierElement = null; try { identifierElement = getSchemaElement.getOnlyChildElementWithSameNamespace(IDENTIFIER); - } catch (MissingNameSpaceException e) { + } catch (final MissingNameSpaceException e) { LOG.trace("Can't get identifier element as only child element with same namespace due to ",e); throw NetconfDocumentedException.wrap(e); } identifier = identifierElement.getTextContent(); - Optional versionElement = getSchemaElement + final Optional versionElement = getSchemaElement .getOnlyChildElementWithSameNamespaceOptionally(VERSION); if (versionElement.isPresent()) { version = Optional.of(versionElement.get().getTextContent()); diff --git a/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringActivator.java b/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringActivator.java index 1411350cd3..1f094f1caf 100644 --- a/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringActivator.java +++ b/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringActivator.java @@ -7,6 +7,10 @@ */ package org.opendaylight.controller.netconf.monitoring.osgi; +import java.util.Collections; +import java.util.Set; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; import org.osgi.framework.BundleActivator; @@ -31,22 +35,43 @@ public class NetconfMonitoringActivator implements BundleActivator { if(monitor!=null) { try { monitor.close(); - } catch (Exception e) { + } catch (final Exception e) { LOG.warn("Ignoring exception while closing {}", monitor, e); } } } - public static class NetconfMonitoringOperationServiceFactory implements NetconfOperationServiceFactory { + public static class NetconfMonitoringOperationServiceFactory implements NetconfOperationServiceFactory, AutoCloseable { + private final NetconfMonitoringOperationService operationService; - public NetconfMonitoringOperationServiceFactory(NetconfMonitoringOperationService operationService) { + private static final AutoCloseable AUTO_CLOSEABLE = new AutoCloseable() { + @Override + public void close() throws Exception { + // NOOP + } + }; + + public NetconfMonitoringOperationServiceFactory(final NetconfMonitoringOperationService operationService) { this.operationService = operationService; } @Override - public NetconfOperationService createService(String netconfSessionIdForReporting) { + public NetconfOperationService createService(final String netconfSessionIdForReporting) { return operationService; } + + @Override + public Set getCapabilities() { + return Collections.emptySet(); + } + + @Override + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + return AUTO_CLOSEABLE; + } + + @Override + public void close() {} } } diff --git a/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringOperationService.java b/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringOperationService.java index a17e139131..efe71e9127 100644 --- a/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringOperationService.java +++ b/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringOperationService.java @@ -7,67 +7,25 @@ */ package org.opendaylight.controller.netconf.monitoring.osgi; -import com.google.common.base.Optional; import com.google.common.collect.Sets; -import java.util.Collection; -import java.util.Collections; import java.util.Set; import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; -import org.opendaylight.controller.netconf.mapping.api.Capability; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; import org.opendaylight.controller.netconf.monitoring.Get; -import org.opendaylight.controller.netconf.monitoring.MonitoringConstants; +import org.opendaylight.controller.netconf.monitoring.GetSchema; public class NetconfMonitoringOperationService implements NetconfOperationService { - private static final Set CAPABILITIES = Sets.newHashSet(new Capability() { - - @Override - public String getCapabilityUri() { - return MonitoringConstants.URI; - } - - @Override - public Optional getModuleNamespace() { - return Optional.of(MonitoringConstants.NAMESPACE); - } - - @Override - public Optional getModuleName() { - return Optional.of(MonitoringConstants.MODULE_NAME); - } - - @Override - public Optional getRevision() { - return Optional.of(MonitoringConstants.MODULE_REVISION); - } - - @Override - public Optional getCapabilitySchema() { - return Optional.absent(); - } - - @Override - public Collection getLocation() { - return Collections.emptyList(); - } - }); - private final NetconfMonitoringService monitor; public NetconfMonitoringOperationService(final NetconfMonitoringService monitor) { this.monitor = monitor; } - @Override - public Set getCapabilities() { - return CAPABILITIES; - } - @Override public Set getNetconfOperations() { - return Sets.newHashSet(new Get(monitor)); + return Sets.newHashSet(new Get(monitor), new GetSchema(monitor)); } @Override diff --git a/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringServiceTracker.java b/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringServiceTracker.java index 0b4d1c2688..5d0a2a91ad 100644 --- a/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringServiceTracker.java +++ b/opendaylight/netconf/netconf-monitoring/src/main/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringServiceTracker.java @@ -8,8 +8,10 @@ package org.opendaylight.controller.netconf.monitoring.osgi; import com.google.common.base.Preconditions; +import java.util.Dictionary; import java.util.Hashtable; import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; +import org.opendaylight.controller.netconf.api.util.NetconfConstants; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; @@ -23,6 +25,7 @@ public class NetconfMonitoringServiceTracker extends ServiceTracker reg; + private NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory factory; NetconfMonitoringServiceTracker(final BundleContext context) { super(context, NetconfMonitoringService.class, null); @@ -36,10 +39,12 @@ public class NetconfMonitoringServiceTracker extends ServiceTracker()); + Dictionary properties = new Hashtable<>(); + properties.put(NetconfConstants.SERVICE_NAME, NetconfConstants.NETCONF_MONITORING); + reg = context.registerService(NetconfOperationServiceFactory.class, factory, properties); return netconfMonitoringService; } @@ -54,6 +59,9 @@ public class NetconfMonitoringServiceTracker extends ServiceTracker\n" + " threadpool-api\n" + @@ -45,15 +46,16 @@ public class DefaultGetSchemaTest { @Test(expected = NetconfDocumentedException.class) public void testDefaultGetSchema() throws Exception { - DefaultGetSchema schema = new DefaultGetSchema(cap, ""); + GetSchema schema = new GetSchema(cap); doThrow(IllegalStateException.class).when(cap).getSchemaForCapability(anyString(), any(Optional.class)); schema.handleWithNoSubsequentOperations(doc, XmlElement.fromDomElement(XmlUtil.readXmlToElement(getSchema))); } @Test public void handleWithNoSubsequentOperations() throws Exception { - DefaultGetSchema schema = new DefaultGetSchema(cap, ""); + GetSchema schema = new GetSchema(cap); doReturn("").when(cap).getSchemaForCapability(anyString(), any(Optional.class)); assertNotNull(schema.handleWithNoSubsequentOperations(doc, XmlElement.fromDomElement(XmlUtil.readXmlToElement(getSchema)))); } -} + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-monitoring/src/test/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringOperationServiceTest.java b/opendaylight/netconf/netconf-monitoring/src/test/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringOperationServiceTest.java index 7873183188..67a54366cd 100644 --- a/opendaylight/netconf/netconf-monitoring/src/test/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringOperationServiceTest.java +++ b/opendaylight/netconf/netconf-monitoring/src/test/java/org/opendaylight/controller/netconf/monitoring/osgi/NetconfMonitoringOperationServiceTest.java @@ -11,25 +11,17 @@ package org.opendaylight.controller.netconf.monitoring.osgi; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; -import com.google.common.base.Optional; -import java.util.Collections; import org.junit.Test; import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; -import org.opendaylight.controller.netconf.monitoring.MonitoringConstants; public class NetconfMonitoringOperationServiceTest { @Test public void testGetters() throws Exception { NetconfMonitoringService monitor = mock(NetconfMonitoringService.class); NetconfMonitoringOperationService service = new NetconfMonitoringOperationService(monitor); + NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory serviceFactory = new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory(service); - assertEquals(1, service.getNetconfOperations().size()); + assertEquals(2, service.getNetconfOperations().size()); - assertEquals(Optional.absent(), service.getCapabilities().iterator().next().getCapabilitySchema()); - assertEquals(Collections.emptyList(), service.getCapabilities().iterator().next().getLocation()); - assertEquals(Optional.of(MonitoringConstants.MODULE_REVISION), service.getCapabilities().iterator().next().getRevision()); - assertEquals(Optional.of(MonitoringConstants.MODULE_NAME), service.getCapabilities().iterator().next().getModuleName()); - assertEquals(Optional.of(MonitoringConstants.NAMESPACE), service.getCapabilities().iterator().next().getModuleNamespace()); - assertEquals(MonitoringConstants.URI, service.getCapabilities().iterator().next().getCapabilityUri()); } } diff --git a/opendaylight/netconf/netconf-monitoring/src/test/java/org/opendaylight/controller/netconf/monitoring/xml/JaxBSerializerTest.java b/opendaylight/netconf/netconf-monitoring/src/test/java/org/opendaylight/controller/netconf/monitoring/xml/JaxBSerializerTest.java index 4b5dcd7d55..a8236b2ebc 100644 --- a/opendaylight/netconf/netconf-monitoring/src/test/java/org/opendaylight/controller/netconf/monitoring/xml/JaxBSerializerTest.java +++ b/opendaylight/netconf/netconf-monitoring/src/test/java/org/opendaylight/controller/netconf/monitoring/xml/JaxBSerializerTest.java @@ -11,9 +11,13 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import com.google.common.base.Optional; import com.google.common.collect.Lists; +import java.util.Set; import org.hamcrest.CoreMatchers; import org.junit.Test; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession; import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; import org.opendaylight.controller.netconf.monitoring.xml.model.NetconfState; import org.opendaylight.controller.netconf.util.xml.XmlUtil; @@ -25,6 +29,7 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.mon import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfSsh; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Transport; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Yang; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.SchemasBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Sessions; @@ -42,6 +47,26 @@ public class JaxBSerializerTest { final NetconfMonitoringService service = new NetconfMonitoringService() { + @Override + public void onSessionUp(final NetconfManagementSession session) { + + } + + @Override + public void onSessionDown(final NetconfManagementSession session) { + + } + + @Override + public void onCapabilitiesAdded(final Set addedCaps) { + + } + + @Override + public void onCapabilitiesRemoved(final Set addedCaps) { + + } + @Override public Sessions getSessions() { return new SessionsBuilder().setSession(Lists.newArrayList(getMockSession(NetconfTcp.class), getMockSession(NetconfSsh.class))).build(); @@ -51,6 +76,26 @@ public class JaxBSerializerTest { public Schemas getSchemas() { return new SchemasBuilder().setSchema(Lists.newArrayList(getMockSchema("id", "v1", Yang.class), getMockSchema("id2", "", Yang.class))).build(); } + + @Override + public String getSchemaForCapability(final String moduleName, final Optional revision) { + return null; + } + + @Override + public Capabilities getCapabilities() { + return null; + } + + @Override + public AutoCloseable registerListener(final MonitoringListener listener) { + return new AutoCloseable() { + @Override + public void close() throws Exception { + // NOOP + } + }; + } }; final NetconfState model = new NetconfState(service); final String xml = XmlUtil.toString(new JaxBSerializer().toXml(model)).replaceAll("\\s", ""); diff --git a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/NetconfMessageToEXIEncoder.java b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/NetconfMessageToEXIEncoder.java index aceb6ac520..5d6d1aa083 100644 --- a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/NetconfMessageToEXIEncoder.java +++ b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/NetconfMessageToEXIEncoder.java @@ -30,23 +30,29 @@ public final class NetconfMessageToEXIEncoder extends MessageToByteEncoder + + + + + netconf-subsystem + org.opendaylight.controller + 0.3.0-SNAPSHOT + + 4.0.0 + bundle + netconf-notifications-api + + + + org.opendaylight.controller + netconf-api + + + org.opendaylight.controller + ietf-netconf-notifications + + + org.slf4j + slf4j-api + + + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.netconf.notifications.* + + + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNetconfNotificationListener.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNetconfNotificationListener.java new file mode 100644 index 0000000000..899ab85e92 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNetconfNotificationListener.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; + + +/** + * Listener for base netconf notifications defined in https://tools.ietf.org/html/rfc6470. + * This listener uses generated classes from yang model defined in RFC6470. + * It alleviates the provisioning of base netconf notifications from the code. + */ +public interface BaseNetconfNotificationListener { + + /** + * Callback used to notify about a change in used capabilities + */ + void onCapabilityChanged(NetconfCapabilityChange capabilityChange); + + // TODO add other base notifications + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNotificationPublisherRegistration.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNotificationPublisherRegistration.java new file mode 100644 index 0000000000..7755fc5b2c --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/BaseNotificationPublisherRegistration.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +/** + * Registration for base notification publisher. This registration allows for publishing of base netconf notifications using generated classes + */ +public interface BaseNotificationPublisherRegistration extends NotificationRegistration, BaseNetconfNotificationListener { + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotification.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotification.java new file mode 100644 index 0000000000..efa42c03e9 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotification.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import com.google.common.base.Preconditions; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Special kind of netconf message that contains a timestamp. + */ +public final class NetconfNotification extends NetconfMessage { + + public static final String NOTIFICATION = "notification"; + public static final String NOTIFICATION_NAMESPACE = "urn:ietf:params:netconf:capability:notification:1.0"; + public static final String RFC3339_DATE_FORMAT_BLUEPRINT = "yyyy-MM-dd'T'HH:mm:ssXXX"; + public static final String EVENT_TIME = "eventTime"; + + /** + * Create new notification and capture the timestamp in the constructor + */ + public NetconfNotification(final Document notificationContent) { + this(notificationContent, new Date()); + } + + /** + * Create new notification with provided timestamp + */ + public NetconfNotification(final Document notificationContent, final Date eventTime) { + super(wrapNotification(notificationContent, eventTime)); + } + + private static Document wrapNotification(final Document notificationContent, final Date eventTime) { + Preconditions.checkNotNull(notificationContent); + Preconditions.checkNotNull(eventTime); + + final Element baseNotification = notificationContent.getDocumentElement(); + final Element entireNotification = notificationContent.createElementNS(NOTIFICATION_NAMESPACE, NOTIFICATION); + entireNotification.appendChild(baseNotification); + + final Element eventTimeElement = notificationContent.createElementNS(NOTIFICATION_NAMESPACE, EVENT_TIME); + eventTimeElement.setTextContent(getSerializedEventTime(eventTime)); + entireNotification.appendChild(eventTimeElement); + + notificationContent.appendChild(entireNotification); + return notificationContent; + } + + private static String getSerializedEventTime(final Date eventTime) { + // SimpleDateFormat is not threadsafe, cannot be in a constant + return new SimpleDateFormat(RFC3339_DATE_FORMAT_BLUEPRINT).format(eventTime); + } +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationCollector.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationCollector.java new file mode 100644 index 0000000000..2663a5db5f --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationCollector.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.Stream; + +/** + * Collector of all notifications. Base or generic + */ +public interface NetconfNotificationCollector { + + /** + * Add notification publisher for a particular stream + * + * Implementations should allow for multiple publishers of a single stream + * and its up to implementations to decide how to merge metadata (e.g. description) + * for the same stream when providing information about available stream + * + */ + NotificationPublisherRegistration registerNotificationPublisher(Stream stream); + + /** + * Register base notification publisher + */ + BaseNotificationPublisherRegistration registerBaseNotificationPublisher(); + + /** + * Users of the registry have an option to get notification each time new notification stream gets registered + * This allows for a push model in addition to pull model for retrieving information about available streams. + * + * The listener should receive callbacks for each stream available prior to the registration when its registered + */ + NotificationRegistration registerStreamListener(NetconfNotificationStreamListener listener); + + /** + * Simple listener that receives notifications about changes in stream availability + */ + public interface NetconfNotificationStreamListener { + + /** + * Stream becomes available in the collector (first publisher is registered) + */ + void onStreamRegistered(Stream stream); + + /** + * Stream is not available anymore in the collector (last publisher is unregistered) + */ + void onStreamUnregistered(StreamNameType stream); + } + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationListener.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationListener.java new file mode 100644 index 0000000000..e1da05cd2b --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationListener.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; + +/** + * Generic listener for netconf notifications + */ +public interface NetconfNotificationListener { + + /** + * Callback used to notify the listener about any new notification + */ + void onNotification(StreamNameType stream, NetconfNotification notification); + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationRegistry.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationRegistry.java new file mode 100644 index 0000000000..db2443ec79 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NetconfNotificationRegistry.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.Streams; + +/** + * + */ +public interface NetconfNotificationRegistry { + + /** + * Add listener for a certain notification type + */ + NotificationListenerRegistration registerNotificationListener(StreamNameType stream, NetconfNotificationListener listener); + + /** + * Check stream availability + */ + boolean isStreamAvailable(StreamNameType streamNameType); + + /** + * Get all the streams available + */ + Streams getNotificationPublishers(); + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationListenerRegistration.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationListenerRegistration.java new file mode 100644 index 0000000000..aa8161277c --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationListenerRegistration.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +/** + * Manages the registration of a single listener + */ +public interface NotificationListenerRegistration extends NotificationRegistration { + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationPublisherRegistration.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationPublisherRegistration.java new file mode 100644 index 0000000000..de105fcc0a --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationPublisherRegistration.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +/** + * Registration for notification publisher. This registration allows for publishing any netconf notifications + */ +public interface NotificationPublisherRegistration extends NetconfNotificationListener, NotificationRegistration { + +} diff --git a/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationRegistration.java b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationRegistration.java new file mode 100644 index 0000000000..a7a86a4f7e --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-api/src/main/java/org/opendaylight/controller/netconf/notifications/NotificationRegistration.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications; + +/** + * Generic registration, used as a base for other registration types + */ +public interface NotificationRegistration extends AutoCloseable { + + // Overriden close does not throw any kind of checked exception + + /** + * Close the registration. + */ + @Override + void close(); +} diff --git a/opendaylight/netconf/netconf-notifications-impl/pom.xml b/opendaylight/netconf/netconf-notifications-impl/pom.xml new file mode 100644 index 0000000000..510d9f07e3 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/pom.xml @@ -0,0 +1,70 @@ + + + + + + netconf-subsystem + org.opendaylight.controller + 0.3.0-SNAPSHOT + + 4.0.0 + bundle + netconf-notifications-impl + + + + org.opendaylight.controller + netconf-notifications-api + + + ${project.groupId} + netconf-util + + + org.opendaylight.yangtools + binding-generator-impl + + + org.opendaylight.yangtools + binding-data-codec + + + org.slf4j + slf4j-api + + + xmlunit + xmlunit + + + org.opendaylight.yangtools + mockito-configuration + + + + + + + org.apache.felix + maven-bundle-plugin + + + org.opendaylight.controller.netconf.notifications.impl.osgi.Activator + + + + + org.opendaylight.yangtools + yang-maven-plugin + + + + \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManager.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManager.java new file mode 100644 index 0000000000..d2dbcaf416 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManager.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl; + +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multiset; +import com.google.common.collect.Sets; +import java.util.Map; +import java.util.Set; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; +import org.opendaylight.controller.netconf.notifications.BaseNotificationPublisherRegistration; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationCollector; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationListener; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.notifications.NotificationListenerRegistration; +import org.opendaylight.controller.netconf.notifications.NotificationPublisherRegistration; +import org.opendaylight.controller.netconf.notifications.NotificationRegistration; +import org.opendaylight.controller.netconf.notifications.impl.ops.NotificationsTransformUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.Streams; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.StreamsBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.Stream; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.StreamBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.StreamKey; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ThreadSafe +public class NetconfNotificationManager implements NetconfNotificationCollector, NetconfNotificationRegistry, NetconfNotificationListener, AutoCloseable { + + public static final StreamNameType BASE_STREAM_NAME = new StreamNameType("NETCONF"); + public static final Stream BASE_NETCONF_STREAM; + + static { + BASE_NETCONF_STREAM = new StreamBuilder() + .setName(BASE_STREAM_NAME) + .setKey(new StreamKey(BASE_STREAM_NAME)) + .setReplaySupport(false) + .setDescription("Default Event Stream") + .build(); + } + + private static final Logger LOG = LoggerFactory.getLogger(NetconfNotificationManager.class); + + // TODO excessive synchronization provides thread safety but is most likely not optimal (combination of concurrent collections might improve performance) + // And also calling callbacks from a synchronized block is dangerous since the listeners/publishers can block the whole notification processing + + @GuardedBy("this") + private final Multimap notificationListeners = HashMultimap.create(); + + @GuardedBy("this") + private final Set streamListeners = Sets.newHashSet(); + + @GuardedBy("this") + private final Map streamMetadata = Maps.newHashMap(); + + @GuardedBy("this") + private final Multiset availableStreams = HashMultiset.create(); + + @GuardedBy("this") + private final Set notificationPublishers = Sets.newHashSet(); + + @Override + public synchronized void onNotification(final StreamNameType stream, final NetconfNotification notification) { + LOG.debug("Notification of type {} detected", stream); + if(LOG.isTraceEnabled()) { + LOG.debug("Notification of type {} detected: {}", stream, notification); + } + + for (final GenericNotificationListenerReg listenerReg : notificationListeners.get(BASE_STREAM_NAME)) { + listenerReg.getListener().onNotification(BASE_STREAM_NAME, notification); + } + } + + @Override + public synchronized NotificationListenerRegistration registerNotificationListener(final StreamNameType stream, final NetconfNotificationListener listener) { + Preconditions.checkNotNull(stream); + Preconditions.checkNotNull(listener); + + LOG.trace("Notification listener registered for stream: {}", stream); + + final GenericNotificationListenerReg genericNotificationListenerReg = new GenericNotificationListenerReg(listener) { + @Override + public void close() { + synchronized (NetconfNotificationManager.this) { + LOG.trace("Notification listener unregistered for stream: {}", stream); + super.close(); + } + } + }; + + notificationListeners.put(BASE_STREAM_NAME, genericNotificationListenerReg); + return genericNotificationListenerReg; + } + + @Override + public synchronized Streams getNotificationPublishers() { + return new StreamsBuilder().setStream(Lists.newArrayList(streamMetadata.values())).build(); + } + + @Override + public synchronized boolean isStreamAvailable(final StreamNameType streamNameType) { + return availableStreams.contains(streamNameType); + } + + @Override + public synchronized NotificationRegistration registerStreamListener(final NetconfNotificationStreamListener listener) { + streamListeners.add(listener); + + // Notify about all already available + for (final Stream availableStream : streamMetadata.values()) { + listener.onStreamRegistered(availableStream); + } + + return new NotificationRegistration() { + @Override + public void close() { + synchronized(NetconfNotificationManager.this) { + streamListeners.remove(listener); + } + } + }; + } + + @Override + public synchronized void close() { + // Unregister all listeners + for (final GenericNotificationListenerReg genericNotificationListenerReg : notificationListeners.values()) { + genericNotificationListenerReg.close(); + } + notificationListeners.clear(); + + // Unregister all publishers + for (final GenericNotificationPublisherReg notificationPublisher : notificationPublishers) { + notificationPublisher.close(); + } + notificationPublishers.clear(); + + // Clear stream Listeners + streamListeners.clear(); + } + + @Override + public synchronized NotificationPublisherRegistration registerNotificationPublisher(final Stream stream) { + Preconditions.checkNotNull(stream); + final StreamNameType streamName = stream.getName(); + + LOG.debug("Notification publisher registered for stream: {}", streamName); + if(LOG.isTraceEnabled()) { + LOG.trace("Notification publisher registered for stream: {}", stream); + } + + if(streamMetadata.containsKey(streamName)) { + LOG.warn("Notification stream {} already registered as: {}. Will be reused", streamName, streamMetadata.get(streamName)); + } else { + streamMetadata.put(streamName, stream); + } + + availableStreams.add(streamName); + + final GenericNotificationPublisherReg genericNotificationPublisherReg = new GenericNotificationPublisherReg(this, streamName) { + @Override + public void close() { + synchronized (NetconfNotificationManager.this) { + super.close(); + } + } + }; + + notificationPublishers.add(genericNotificationPublisherReg); + + notifyStreamAdded(stream); + return genericNotificationPublisherReg; + } + + private void unregisterNotificationPublisher(final StreamNameType streamName, final GenericNotificationPublisherReg genericNotificationPublisherReg) { + availableStreams.remove(streamName); + notificationPublishers.remove(genericNotificationPublisherReg); + + LOG.debug("Notification publisher unregistered for stream: {}", streamName); + + // Notify stream listeners if all publishers are gone and also clear metadata for stream + if (!isStreamAvailable(streamName)) { + LOG.debug("Notification stream: {} became unavailable", streamName); + streamMetadata.remove(streamName); + notifyStreamRemoved(streamName); + } + } + + private synchronized void notifyStreamAdded(final Stream stream) { + for (final NetconfNotificationStreamListener streamListener : streamListeners) { + streamListener.onStreamRegistered(stream); + } + } + private synchronized void notifyStreamRemoved(final StreamNameType stream) { + for (final NetconfNotificationStreamListener streamListener : streamListeners) { + streamListener.onStreamUnregistered(stream); + } + } + + @Override + public BaseNotificationPublisherRegistration registerBaseNotificationPublisher() { + final NotificationPublisherRegistration notificationPublisherRegistration = registerNotificationPublisher(BASE_NETCONF_STREAM); + return new BaseNotificationPublisherReg(notificationPublisherRegistration); + } + + private static class GenericNotificationPublisherReg implements NotificationPublisherRegistration { + private NetconfNotificationManager baseListener; + private final StreamNameType registeredStream; + + public GenericNotificationPublisherReg(final NetconfNotificationManager baseListener, final StreamNameType registeredStream) { + this.baseListener = baseListener; + this.registeredStream = registeredStream; + } + + @Override + public void close() { + baseListener.unregisterNotificationPublisher(registeredStream, this); + baseListener = null; + } + + @Override + public void onNotification(final StreamNameType stream, final NetconfNotification notification) { + Preconditions.checkState(baseListener != null, "Already closed"); + Preconditions.checkArgument(stream.equals(registeredStream)); + baseListener.onNotification(stream, notification); + } + } + + private static class BaseNotificationPublisherReg implements BaseNotificationPublisherRegistration { + + private final NotificationPublisherRegistration baseRegistration; + + public BaseNotificationPublisherReg(final NotificationPublisherRegistration baseRegistration) { + this.baseRegistration = baseRegistration; + } + + @Override + public void close() { + baseRegistration.close(); + } + + @Override + public void onCapabilityChanged(final NetconfCapabilityChange capabilityChange) { + baseRegistration.onNotification(BASE_STREAM_NAME, serializeNotification(capabilityChange)); + } + + private static NetconfNotification serializeNotification(final NetconfCapabilityChange capabilityChange) { + return NotificationsTransformUtil.transform(capabilityChange); + } + } + + private class GenericNotificationListenerReg implements NotificationListenerRegistration { + private final NetconfNotificationListener listener; + + public GenericNotificationListenerReg(final NetconfNotificationListener listener) { + this.listener = listener; + } + + public NetconfNotificationListener getListener() { + return listener; + } + + @Override + public void close() { + notificationListeners.remove(BASE_STREAM_NAME, this); + } + } +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscription.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscription.java new file mode 100644 index 0000000000..e8b7413069 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscription.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import java.util.List; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.mapping.api.SessionAwareNetconfOperation; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationListener; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.notifications.NotificationListenerRegistration; +import org.opendaylight.controller.netconf.notifications.impl.NetconfNotificationManager; +import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscriptionInput; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Create subscription listens for create subscription requests and registers notification listeners into notification registry. + * Received notifications are sent to the client right away + */ +public class CreateSubscription extends AbstractLastNetconfOperation implements SessionAwareNetconfOperation, AutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(CreateSubscription.class); + + static final String CREATE_SUBSCRIPTION = "create-subscription"; + + private final NetconfNotificationRegistry notifications; + private final List subscriptions = Lists.newArrayList(); + private NetconfSession netconfSession; + + public CreateSubscription(final String netconfSessionIdForReporting, final NetconfNotificationRegistry notifications) { + super(netconfSessionIdForReporting); + this.notifications = notifications; + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + operationElement.checkName(CREATE_SUBSCRIPTION); + operationElement.checkNamespace(CreateSubscriptionInput.QNAME.getNamespace().toString()); + // FIXME reimplement using CODEC_REGISTRY and parse everything into generated class instance + // Waiting ofr https://git.opendaylight.org/gerrit/#/c/13763/ + + // FIXME filter could be supported same way as netconf server filters get and get-config results + final Optional filter = operationElement.getOnlyChildElementWithSameNamespaceOptionally("filter"); + Preconditions.checkArgument(filter.isPresent() == false, "Filter element not yet supported"); + + // Replay not supported + final Optional startTime = operationElement.getOnlyChildElementWithSameNamespaceOptionally("startTime"); + Preconditions.checkArgument(startTime.isPresent() == false, "StartTime element not yet supported"); + + // Stop time not supported + final Optional stopTime = operationElement.getOnlyChildElementWithSameNamespaceOptionally("stopTime"); + Preconditions.checkArgument(stopTime.isPresent() == false, "StopTime element not yet supported"); + + final StreamNameType streamNameType = parseStreamIfPresent(operationElement); + + Preconditions.checkNotNull(netconfSession); + // Premature streams are allowed (meaning listener can register even if no provider is available yet) + if(notifications.isStreamAvailable(streamNameType) == false) { + LOG.warn("Registering premature stream {}. No publisher available yet for session {}", streamNameType, getNetconfSessionIdForReporting()); + } + + final NotificationListenerRegistration notificationListenerRegistration = + notifications.registerNotificationListener(streamNameType, new NotificationSubscription(netconfSession)); + subscriptions.add(notificationListenerRegistration); + + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + private StreamNameType parseStreamIfPresent(final XmlElement operationElement) throws NetconfDocumentedException { + final Optional stream = operationElement.getOnlyChildElementWithSameNamespaceOptionally("stream"); + return stream.isPresent() ? new StreamNameType(stream.get().getTextContent()) : NetconfNotificationManager.BASE_STREAM_NAME; + } + + @Override + protected String getOperationName() { + return CREATE_SUBSCRIPTION; + } + + @Override + protected String getOperationNamespace() { + return CreateSubscriptionInput.QNAME.getNamespace().toString(); + } + + @Override + public void setSession(final NetconfSession session) { + this.netconfSession = session; + } + + @Override + public void close() { + netconfSession = null; + // Unregister from notification streams + for (final NotificationListenerRegistration subscription : subscriptions) { + subscription.close(); + } + } + + private static class NotificationSubscription implements NetconfNotificationListener { + private final NetconfSession currentSession; + + public NotificationSubscription(final NetconfSession currentSession) { + this.currentSession = currentSession; + } + + @Override + public void onNotification(final StreamNameType stream, final NetconfNotification notification) { + currentSession.sendMessage(notification); + } + } +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/Get.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/Get.java new file mode 100644 index 0000000000..85f29360c5 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/Get.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import com.google.common.base.Preconditions; +import java.io.IOException; +import javax.xml.stream.XMLStreamException; +import javax.xml.transform.dom.DOMResult; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.mapping.api.HandlingPriority; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.util.mapping.AbstractNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.Netconf; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.NetconfBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.Streams; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Serialize the subtree for netconf notifications into the response of get rpc. + * This operation just adds its subtree into the common response of get rpc. + */ +public class Get extends AbstractNetconfOperation implements AutoCloseable { + + private static final String GET = "get"; + private static final InstanceIdentifier NETCONF_SUBTREE_INSTANCE_IDENTIFIER = InstanceIdentifier.builder(Netconf.class).build(); + + private final NetconfNotificationRegistry notificationRegistry; + + public Get(final String netconfSessionIdForReporting, final NetconfNotificationRegistry notificationRegistry) { + super(netconfSessionIdForReporting); + Preconditions.checkNotNull(notificationRegistry); + this.notificationRegistry = notificationRegistry; + } + + @Override + protected String getOperationName() { + return GET; + } + + @Override + public Document handle(final Document requestMessage, final NetconfOperationChainedExecution subsequentOperation) throws NetconfDocumentedException { + final Document partialResponse = subsequentOperation.execute(requestMessage); + final Streams availableStreams = notificationRegistry.getNotificationPublishers(); + if(availableStreams.getStream().isEmpty() == false) { + serializeStreamsSubtree(partialResponse, availableStreams); + } + return partialResponse; + } + + static void serializeStreamsSubtree(final Document partialResponse, final Streams availableStreams) throws NetconfDocumentedException { + final Netconf netconfSubtree = new NetconfBuilder().setStreams(availableStreams).build(); + final NormalizedNode normalized = toNormalized(netconfSubtree); + + final DOMResult result = new DOMResult(getPlaceholder(partialResponse)); + + try { + NotificationsTransformUtil.writeNormalizedNode(normalized, result, SchemaPath.ROOT); + } catch (final XMLStreamException | IOException e) { + throw new IllegalStateException("Unable to serialize " + netconfSubtree, e); + } + } + + private static Element getPlaceholder(final Document innerResult) + throws NetconfDocumentedException { + final XmlElement rootElement = XmlElement.fromDomElementWithExpected( + innerResult.getDocumentElement(), XmlNetconfConstants.RPC_REPLY_KEY, XmlNetconfConstants.RFC4741_TARGET_NAMESPACE); + return rootElement.getOnlyChildElement(XmlNetconfConstants.DATA_KEY).getDomElement(); + } + + private static NormalizedNode toNormalized(final Netconf netconfSubtree) { + return NotificationsTransformUtil.CODEC_REGISTRY.toNormalizedNode(NETCONF_SUBTREE_INSTANCE_IDENTIFIER, netconfSubtree).getValue(); + } + + @Override + protected Element handle(final Document document, final XmlElement message, final NetconfOperationChainedExecution subsequentOperation) + throws NetconfDocumentedException { + throw new UnsupportedOperationException("Never gets called"); + } + + @Override + protected HandlingPriority getHandlingPriority() { + return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY.increasePriority(2); + } + + @Override + public void close() throws Exception { + + } +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtil.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtil.java new file mode 100644 index 0000000000..080176dcd4 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtil.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import javassist.ClassPool; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.dom.DOMResult; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.$YangModuleInfoImpl; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator; +import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry; +import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext; +import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext; +import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; + +public final class NotificationsTransformUtil { + + private static final Logger LOG = LoggerFactory.getLogger(NotificationsTransformUtil.class); + + private NotificationsTransformUtil() {} + + static final SchemaContext NOTIFICATIONS_SCHEMA_CTX; + static final BindingNormalizedNodeCodecRegistry CODEC_REGISTRY; + static final XMLOutputFactory XML_FACTORY; + static final RpcDefinition CREATE_SUBSCRIPTION_RPC; + + static final SchemaPath CAPABILITY_CHANGE_SCHEMA_PATH = SchemaPath.create(true, NetconfCapabilityChange.QNAME); + + static { + XML_FACTORY = XMLOutputFactory.newFactory(); + XML_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); + + final ModuleInfoBackedContext moduleInfoBackedContext = ModuleInfoBackedContext.create(); + moduleInfoBackedContext.addModuleInfos(Collections.singletonList($YangModuleInfoImpl.getInstance())); + moduleInfoBackedContext.addModuleInfos(Collections.singletonList(org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.$YangModuleInfoImpl.getInstance())); + final Optional schemaContextOptional = moduleInfoBackedContext.tryToCreateSchemaContext(); + Preconditions.checkState(schemaContextOptional.isPresent()); + NOTIFICATIONS_SCHEMA_CTX = schemaContextOptional.get(); + + CREATE_SUBSCRIPTION_RPC = Preconditions.checkNotNull(findCreateSubscriptionRpc()); + + Preconditions.checkNotNull(CREATE_SUBSCRIPTION_RPC); + + final JavassistUtils javassist = JavassistUtils.forClassPool(ClassPool.getDefault()); + CODEC_REGISTRY = new BindingNormalizedNodeCodecRegistry(StreamWriterGenerator.create(javassist)); + CODEC_REGISTRY.onBindingRuntimeContextUpdated(BindingRuntimeContext.create(moduleInfoBackedContext, NOTIFICATIONS_SCHEMA_CTX)); + } + + private static RpcDefinition findCreateSubscriptionRpc() { + return Iterables.getFirst(Collections2.filter(NOTIFICATIONS_SCHEMA_CTX.getOperations(), new Predicate() { + @Override + public boolean apply(final RpcDefinition input) { + return input.getQName().getLocalName().equals(CreateSubscription.CREATE_SUBSCRIPTION); + } + }), null); + } + + /** + * Transform base notification for capabilities into NetconfNotification + */ + public static NetconfNotification transform(final NetconfCapabilityChange capabilityChange) { + return transform(capabilityChange, Optional.absent()); + } + + public static NetconfNotification transform(final NetconfCapabilityChange capabilityChange, final Date eventTime) { + return transform(capabilityChange, Optional.fromNullable(eventTime)); + } + + private static NetconfNotification transform(final NetconfCapabilityChange capabilityChange, final Optional eventTime) { + final ContainerNode containerNode = CODEC_REGISTRY.toNormalizedNodeNotification(capabilityChange); + final DOMResult result = new DOMResult(XmlUtil.newDocument()); + try { + writeNormalizedNode(containerNode, result, CAPABILITY_CHANGE_SCHEMA_PATH); + } catch (final XMLStreamException| IOException e) { + throw new IllegalStateException("Unable to serialize " + capabilityChange, e); + } + final Document node = (Document) result.getNode(); + return eventTime.isPresent() ? + new NetconfNotification(node, eventTime.get()): + new NetconfNotification(node); + } + + static void writeNormalizedNode(final NormalizedNode normalized, final DOMResult result, final SchemaPath schemaPath) throws IOException, XMLStreamException { + NormalizedNodeWriter normalizedNodeWriter = null; + NormalizedNodeStreamWriter normalizedNodeStreamWriter = null; + XMLStreamWriter writer = null; + try { + writer = XML_FACTORY.createXMLStreamWriter(result); + normalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(writer, NOTIFICATIONS_SCHEMA_CTX, schemaPath); + normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(normalizedNodeStreamWriter); + + normalizedNodeWriter.write(normalized); + + normalizedNodeWriter.flush(); + } finally { + try { + if(normalizedNodeWriter != null) { + normalizedNodeWriter.close(); + } + if(normalizedNodeStreamWriter != null) { + normalizedNodeStreamWriter.close(); + } + if(writer != null) { + writer.close(); + } + } catch (final Exception e) { + LOG.warn("Unable to close resource properly", e); + } + } + } + +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/osgi/Activator.java b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/osgi/Activator.java new file mode 100644 index 0000000000..a0a0dfcb8f --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/osgi/Activator.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.osgi; + +import com.google.common.base.Optional; +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Set; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.netconf.api.util.NetconfConstants; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationCollector; +import org.opendaylight.controller.netconf.notifications.impl.NetconfNotificationManager; +import org.opendaylight.controller.netconf.notifications.impl.ops.CreateSubscription; +import org.opendaylight.controller.netconf.notifications.impl.ops.Get; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +public class Activator implements BundleActivator { + + private ServiceRegistration netconfNotificationCollectorServiceRegistration; + private ServiceRegistration operationaServiceRegistration; + private NetconfNotificationManager netconfNotificationManager; + + @Override + public void start(final BundleContext context) throws Exception { + netconfNotificationManager = new NetconfNotificationManager(); + netconfNotificationCollectorServiceRegistration = context.registerService(NetconfNotificationCollector.class, netconfNotificationManager, new Hashtable()); + + final NetconfOperationServiceFactory netconfOperationServiceFactory = new NetconfOperationServiceFactory() { + + private final Set capabilities = Collections.singleton(new NotificationsCapability()); + + @Override + public Set getCapabilities() { + return capabilities; + } + + @Override + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + listener.onCapabilitiesAdded(capabilities); + return new AutoCloseable() { + @Override + public void close() { + listener.onCapabilitiesRemoved(capabilities); + } + }; + } + + @Override + public NetconfOperationService createService(final String netconfSessionIdForReporting) { + return new NetconfOperationService() { + + private final CreateSubscription createSubscription = new CreateSubscription(netconfSessionIdForReporting, netconfNotificationManager); + + + @Override + public Set getNetconfOperations() { + return Sets.newHashSet( + new Get(netconfSessionIdForReporting, netconfNotificationManager), + createSubscription); + } + + @Override + public void close() { + createSubscription.close(); + } + }; + } + }; + + final Dictionary properties = new Hashtable<>(); + properties.put(NetconfConstants.SERVICE_NAME, NetconfConstants.NETCONF_MONITORING); + operationaServiceRegistration = context.registerService(NetconfOperationServiceFactory.class, netconfOperationServiceFactory, properties); + + } + + @Override + public void stop(final BundleContext context) throws Exception { + if(netconfNotificationCollectorServiceRegistration != null) { + netconfNotificationCollectorServiceRegistration.unregister(); + netconfNotificationCollectorServiceRegistration = null; + } + if (netconfNotificationManager != null) { + netconfNotificationManager.close(); + } + if (operationaServiceRegistration != null) { + operationaServiceRegistration.unregister(); + operationaServiceRegistration = null; + } + } + + private class NotificationsCapability implements Capability { + @Override + public String getCapabilityUri() { + return NetconfNotification.NOTIFICATION_NAMESPACE; + } + + @Override + public Optional getModuleNamespace() { + return Optional.absent(); + } + + @Override + public Optional getModuleName() { + return Optional.absent(); + } + + @Override + public Optional getRevision() { + return Optional.absent(); + } + + @Override + public Optional getCapabilitySchema() { + return Optional.absent(); + } + + @Override + public Collection getLocation() { + return Collections.emptyList(); + } + } +} diff --git a/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManagerTest.java b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManagerTest.java new file mode 100644 index 0000000000..36d2015ab7 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/NetconfNotificationManagerTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl; + +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.netconf.notifications.BaseNotificationPublisherRegistration; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationCollector; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationListener; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.notifications.NotificationListenerRegistration; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.Stream; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChangeBuilder; + +public class NetconfNotificationManagerTest { + + @Mock + private NetconfNotificationRegistry notificationRegistry; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testNotificationListeners() throws Exception { + final NetconfNotificationManager netconfNotificationManager = new NetconfNotificationManager(); + final BaseNotificationPublisherRegistration baseNotificationPublisherRegistration = + netconfNotificationManager.registerBaseNotificationPublisher(); + + final NetconfCapabilityChangeBuilder capabilityChangedBuilder = new NetconfCapabilityChangeBuilder(); + + final NetconfNotificationListener listener = mock(NetconfNotificationListener.class); + doNothing().when(listener).onNotification(any(StreamNameType.class), any(NetconfNotification.class)); + final NotificationListenerRegistration notificationListenerRegistration = netconfNotificationManager.registerNotificationListener(NetconfNotificationManager.BASE_NETCONF_STREAM.getName(), listener); + final NetconfCapabilityChange notification = capabilityChangedBuilder.build(); + baseNotificationPublisherRegistration.onCapabilityChanged(notification); + + verify(listener).onNotification(any(StreamNameType.class), any(NetconfNotification.class)); + + notificationListenerRegistration.close(); + + baseNotificationPublisherRegistration.onCapabilityChanged(notification); + verifyNoMoreInteractions(listener); + } + + @Test + public void testClose() throws Exception { + final NetconfNotificationManager netconfNotificationManager = new NetconfNotificationManager(); + + final BaseNotificationPublisherRegistration baseNotificationPublisherRegistration = netconfNotificationManager.registerBaseNotificationPublisher(); + + final NetconfNotificationListener listener = mock(NetconfNotificationListener.class); + doNothing().when(listener).onNotification(any(StreamNameType.class), any(NetconfNotification.class)); + + netconfNotificationManager.registerNotificationListener(NetconfNotificationManager.BASE_NETCONF_STREAM.getName(), listener); + + final NetconfNotificationCollector.NetconfNotificationStreamListener streamListener = + mock(NetconfNotificationCollector.NetconfNotificationStreamListener.class); + doNothing().when(streamListener).onStreamUnregistered(any(StreamNameType.class)); + doNothing().when(streamListener).onStreamRegistered(any(Stream.class)); + netconfNotificationManager.registerStreamListener(streamListener); + + verify(streamListener).onStreamRegistered(NetconfNotificationManager.BASE_NETCONF_STREAM); + + netconfNotificationManager.close(); + + verify(streamListener).onStreamUnregistered(NetconfNotificationManager.BASE_NETCONF_STREAM.getName()); + + try { + baseNotificationPublisherRegistration.onCapabilityChanged(new NetconfCapabilityChangeBuilder().build()); + } catch (final IllegalStateException e) { + // Exception should be thrown after manager is closed + return; + } + + fail("Publishing into a closed manager should fail"); + } + + @Test + public void testStreamListeners() throws Exception { + final NetconfNotificationManager netconfNotificationManager = new NetconfNotificationManager(); + + final NetconfNotificationCollector.NetconfNotificationStreamListener streamListener = mock(NetconfNotificationCollector.NetconfNotificationStreamListener.class); + doNothing().when(streamListener).onStreamRegistered(any(Stream.class)); + doNothing().when(streamListener).onStreamUnregistered(any(StreamNameType.class)); + + netconfNotificationManager.registerStreamListener(streamListener); + + final BaseNotificationPublisherRegistration baseNotificationPublisherRegistration = + netconfNotificationManager.registerBaseNotificationPublisher(); + + verify(streamListener).onStreamRegistered(NetconfNotificationManager.BASE_NETCONF_STREAM); + + + baseNotificationPublisherRegistration.close(); + + verify(streamListener).onStreamUnregistered(NetconfNotificationManager.BASE_STREAM_NAME); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscriptionTest.java b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscriptionTest.java new file mode 100644 index 0000000000..aca8f2de91 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/CreateSubscriptionTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import org.hamcrest.CoreMatchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.netconf.api.NetconfSession; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationListener; +import org.opendaylight.controller.netconf.notifications.NetconfNotificationRegistry; +import org.opendaylight.controller.netconf.notifications.NotificationListenerRegistration; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.w3c.dom.Element; + +public class CreateSubscriptionTest { + + private static final String CREATE_SUBSCRIPTION_XML = "\n" + + "TESTSTREAM" + + ""; + + @Mock + private NetconfNotificationRegistry notificationRegistry; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doReturn(true).when(notificationRegistry).isStreamAvailable(any(StreamNameType.class)); + doReturn(mock(NotificationListenerRegistration.class)).when(notificationRegistry).registerNotificationListener(any(StreamNameType.class), any(NetconfNotificationListener.class)); + } + + @Test + public void testHandleWithNoSubsequentOperations() throws Exception { + final CreateSubscription createSubscription = new CreateSubscription("id", notificationRegistry); + createSubscription.setSession(mock(NetconfSession.class)); + + final Element e = XmlUtil.readXmlToElement(CREATE_SUBSCRIPTION_XML); + + final XmlElement operationElement = XmlElement.fromDomElement(e); + final Element element = createSubscription.handleWithNoSubsequentOperations(XmlUtil.newDocument(), operationElement); + + Assert.assertThat(XmlUtil.toString(element), CoreMatchers.containsString("ok")); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/GetTest.java b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/GetTest.java new file mode 100644 index 0000000000..6f38f24f10 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/GetTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.Lists; +import java.io.IOException; +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.XMLUnit; +import org.junit.Test; +import org.opendaylight.controller.netconf.notifications.impl.ops.Get; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.StreamNameType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.Streams; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.StreamsBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.StreamBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netmod.notification.rev080714.netconf.streams.StreamKey; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +public class GetTest { + + @Test + public void testSerializeStreamsSubtree() throws Exception { + final StreamsBuilder streamsBuilder = new StreamsBuilder(); + final StreamBuilder streamBuilder = new StreamBuilder(); + final StreamNameType base = new StreamNameType("base"); + streamBuilder.setName(base); + streamBuilder.setKey(new StreamKey(base)); + streamBuilder.setDescription("description"); + streamBuilder.setReplaySupport(false); + streamsBuilder.setStream(Lists.newArrayList(streamBuilder.build())); + final Streams streams = streamsBuilder.build(); + + final Document response = getBlankResponse(); + Get.serializeStreamsSubtree(response, streams); + final Diff diff = XMLUnit.compareXML(XmlUtil.toString(response), + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "base\n" + + "description\n" + + "false\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"); + + assertTrue(diff.toString(), diff.identical()); + } + + private Document getBlankResponse() throws IOException, SAXException { + + return XmlUtil.readXmlToDocument("\n" + + "\n" + + "\n" + + ""); + } +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtilTest.java b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtilTest.java new file mode 100644 index 0000000000..c4bc41cf0f --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/test/java/org/opendaylight/controller/netconf/notifications/impl/ops/NotificationsTransformUtilTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.notifications.impl.ops; + +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.Lists; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.XMLUnit; +import org.junit.Test; +import org.opendaylight.controller.netconf.notifications.NetconfNotification; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChangeBuilder; + +public class NotificationsTransformUtilTest { + + private static final Date DATE = new Date(); + private static final String innerNotification = "" + + "uri3" + + "uri4" + + "uri1" + + ""; + + private static final String expectedNotification = "" + + innerNotification + + "" + new SimpleDateFormat(NetconfNotification.RFC3339_DATE_FORMAT_BLUEPRINT).format(DATE) + "" + + ""; + + @Test + public void testTransform() throws Exception { + final NetconfCapabilityChangeBuilder netconfCapabilityChangeBuilder = new NetconfCapabilityChangeBuilder(); + + netconfCapabilityChangeBuilder.setAddedCapability(Lists.newArrayList(new Uri("uri1"), new Uri("uri1"))); + netconfCapabilityChangeBuilder.setDeletedCapability(Lists.newArrayList(new Uri("uri3"), new Uri("uri4"))); + + final NetconfCapabilityChange capabilityChange = netconfCapabilityChangeBuilder.build(); + final NetconfNotification transform = NotificationsTransformUtil.transform(capabilityChange, DATE); + + final String serialized = XmlUtil.toString(transform.getDocument()); + + XMLUnit.setIgnoreWhitespace(true); + final Diff diff = XMLUnit.compareXML(expectedNotification, serialized); + assertTrue(diff.toString(), diff.similar()); + } + + @Test + public void testTransformFromDOM() throws Exception { + final NetconfNotification netconfNotification = new NetconfNotification(XmlUtil.readXmlToDocument(innerNotification), DATE); + + XMLUnit.setIgnoreWhitespace(true); + final Diff diff = XMLUnit.compareXML(expectedNotification, netconfNotification.toString()); + assertTrue(diff.toString(), diff.similar()); + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-ssh/pom.xml b/opendaylight/netconf/netconf-ssh/pom.xml index 2371ede577..03f7a2fac2 100644 --- a/opendaylight/netconf/netconf-ssh/pom.xml +++ b/opendaylight/netconf/netconf-ssh/pom.xml @@ -24,9 +24,27 @@ ${project.groupId} netconf-util + + ${project.groupId} + netconf-impl + commons-io commons-io + + + org.opendaylight.controller + threadpool-config-api + + + org.opendaylight.controller + netty-config-api + 0.3.0-SNAPSHOT + + + + org.opendaylight.yangtools.model + ietf-inet-types org.bouncycastle @@ -87,6 +105,41 @@ + + org.opendaylight.yangtools + yang-maven-plugin + + + config + + generate-sources + + + + + org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator + ${jmxGeneratorPath} + + urn:opendaylight:params:xml:ns:yang:controller==org.opendaylight.controller.config.yang + + + + org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl + ${salGeneratorPath} + + + true + + + + + + org.opendaylight.controller + yang-jmx-generator-plugin + ${config.version} + + + diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/config/yang/netconf/northbound/ssh/NetconfNorthboundSshModule.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/config/yang/netconf/northbound/ssh/NetconfNorthboundSshModule.java new file mode 100644 index 0000000000..f17bc3cceb --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/config/yang/netconf/northbound/ssh/NetconfNorthboundSshModule.java @@ -0,0 +1,122 @@ +package org.opendaylight.controller.config.yang.netconf.northbound.ssh; + +import io.netty.channel.ChannelFuture; +import io.netty.channel.local.LocalAddress; +import io.netty.util.concurrent.GenericFutureListener; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.concurrent.Executors; +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider; +import org.apache.sshd.server.session.ServerSession; +import org.opendaylight.controller.netconf.api.NetconfServerDispatcher; +import org.opendaylight.controller.netconf.ssh.SshProxyServer; +import org.opendaylight.controller.netconf.ssh.SshProxyServerConfigurationBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NetconfNorthboundSshModule extends org.opendaylight.controller.config.yang.netconf.northbound.ssh.AbstractNetconfNorthboundSshModule { + + private static final Logger LOG = LoggerFactory.getLogger(NetconfNorthboundSshModule.class); + + public NetconfNorthboundSshModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { + super(identifier, dependencyResolver); + } + + public NetconfNorthboundSshModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, final org.opendaylight.controller.config.yang.netconf.northbound.ssh.NetconfNorthboundSshModule oldModule, final java.lang.AutoCloseable oldInstance) { + super(identifier, dependencyResolver, oldModule, oldInstance); + } + + @Override + public void customValidation() { + // add custom validation form module attributes here. + } + + @Override + public java.lang.AutoCloseable createInstance() { + final NetconfServerDispatcher dispatch = getDispatcherDependency(); + + final LocalAddress localAddress = new LocalAddress(getPort().toString()); + final ChannelFuture localServer = dispatch.createLocalServer(localAddress); + + final SshProxyServer sshProxyServer = new SshProxyServer(Executors.newScheduledThreadPool(1), getWorkerThreadGroupDependency(), getEventExecutorDependency()); + + final InetSocketAddress bindingAddress = getInetAddress(); + final SshProxyServerConfigurationBuilder sshProxyServerConfigurationBuilder = new SshProxyServerConfigurationBuilder(); + sshProxyServerConfigurationBuilder.setBindingAddress(bindingAddress); + sshProxyServerConfigurationBuilder.setLocalAddress(localAddress); + sshProxyServerConfigurationBuilder.setAuthenticator(new UserAuthenticator(getUsername(), getPassword())); + sshProxyServerConfigurationBuilder.setIdleTimeout(Integer.MAX_VALUE); + sshProxyServerConfigurationBuilder.setKeyPairProvider(new PEMGeneratorHostKeyProvider()); + + localServer.addListener(new GenericFutureListener() { + + @Override + public void operationComplete(final ChannelFuture future) { + if(future.isDone() && !future.isCancelled()) { + try { + sshProxyServer.bind(sshProxyServerConfigurationBuilder.createSshProxyServerConfiguration()); + LOG.info("Netconf SSH endpoint started successfully at {}", bindingAddress); + } catch (final IOException e) { + throw new RuntimeException("Unable to start SSH netconf server", e); + } + } else { + LOG.warn("Unable to start SSH netconf server at {}", bindingAddress, future.cause()); + throw new RuntimeException("Unable to start SSH netconf server", future.cause()); + } + } + }); + + return new NetconfServerCloseable(localServer, sshProxyServer); + } + + private InetSocketAddress getInetAddress() { + try { + final InetAddress inetAd = InetAddress.getByName(getBindingAddress().getIpv4Address() == null ? getBindingAddress().getIpv6Address().getValue() : getBindingAddress().getIpv4Address().getValue()); + return new InetSocketAddress(inetAd, getPort().getValue()); + } catch (final UnknownHostException e) { + throw new IllegalArgumentException("Unable to bind netconf endpoint to address " + getBindingAddress(), e); + } + } + + private static final class NetconfServerCloseable implements AutoCloseable { + private final ChannelFuture localServer; + private final SshProxyServer sshProxyServer; + + public NetconfServerCloseable(final ChannelFuture localServer, final SshProxyServer sshProxyServer) { + this.localServer = localServer; + this.sshProxyServer = sshProxyServer; + } + + @Override + public void close() throws Exception { + sshProxyServer.close(); + + if(localServer.isDone()) { + localServer.channel().close(); + } else { + localServer.cancel(true); + } + } + } + + + private static final class UserAuthenticator implements PasswordAuthenticator { + + private final String username; + private final String password; + + public UserAuthenticator(final String username, final String password) { + this.username = username; + this.password = password; + } + + @Override + public boolean authenticate(final String username, final String password, final ServerSession session) { + // FIXME use aaa stuff here instead + return this.username.equals(username) && this.password.equals(password); + } + } +} diff --git a/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/config/yang/netconf/northbound/ssh/NetconfNorthboundSshModuleFactory.java b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/config/yang/netconf/northbound/ssh/NetconfNorthboundSshModuleFactory.java new file mode 100644 index 0000000000..4e107ec24d --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/java/org/opendaylight/controller/config/yang/netconf/northbound/ssh/NetconfNorthboundSshModuleFactory.java @@ -0,0 +1,13 @@ +/* +* Generated file +* +* Generated from: yang module name: netconf-northbound-ssh yang module local name: netconf-northbound-ssh +* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator +* Generated at: Mon Feb 09 14:09:07 CET 2015 +* +* Do not modify this file unless it is present under src/main directory +*/ +package org.opendaylight.controller.config.yang.netconf.northbound.ssh; +public class NetconfNorthboundSshModuleFactory extends org.opendaylight.controller.config.yang.netconf.northbound.ssh.AbstractNetconfNorthboundSshModuleFactory { + +} diff --git a/opendaylight/netconf/netconf-ssh/src/main/yang/netconf-northbound-ssh.yang b/opendaylight/netconf/netconf-ssh/src/main/yang/netconf-northbound-ssh.yang new file mode 100644 index 0000000000..2e9d0b15b4 --- /dev/null +++ b/opendaylight/netconf/netconf-ssh/src/main/yang/netconf-northbound-ssh.yang @@ -0,0 +1,94 @@ +module netconf-northbound-ssh { + yang-version 1; + namespace "urn:opendaylight:params:xml:ns:yang:controller:netconf:northbound:ssh"; + prefix "nni"; + + import netconf-northbound-mapper { prefix nnm; revision-date 2015-01-14; } + import netconf-northbound { prefix nn; revision-date 2015-01-14; } + import config { prefix config; revision-date 2013-04-05; } + import threadpool {prefix th;} + import netty {prefix netty;} + import ietf-inet-types { prefix inet; revision-date 2010-09-24; } + + organization "Cisco Systems, Inc."; + + description + "This module contains the base YANG definitions for + a default implementation of netconf northbound server"; + + revision "2015-01-14" { + description + "Initial revision."; + } + + identity netconf-northbound-ssh { + base config:module-type; + config:java-name-prefix NetconfNorthboundSsh; + } + + augment "/config:modules/config:module/config:configuration" { + case netconf-northbound-ssh { + when "/config:modules/config:module/config:type = 'netconf-northbound-ssh'"; + + leaf port { + type inet:port-number; + default 2830; + } + + leaf binding-address { + type inet:ip-address; + default "0.0.0.0"; + } + + container processing-executor { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity th:scheduled-threadpool; + } + } + + description "Required by the mina-ssh library used in SSH endpoint"; + } + + container event-executor { + uses config:service-ref { + refine type { + mandatory true; + config:required-identity netty:netty-event-executor; + } + } + } + + container worker-thread-group { + uses config:service-ref { + refine type { + config:required-identity netty:netty-threadgroup; + } + } + } + + container dispatcher { + uses config:service-ref { + refine type { + config:required-identity nn:netconf-server-dispatcher; + } + } + } + + // FIXME use auth provider from aaa instead + leaf username { + description "Specifies username credential"; + type string; + } + + leaf password { + description "Specifies password credential"; + type string; + } + + + } + } + +} \ No newline at end of file diff --git a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java index 72534e242e..ab73126021 100644 --- a/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java +++ b/opendaylight/netconf/netconf-ssh/src/test/java/org/opendaylight/controller/netconf/netty/SSHTest.java @@ -84,7 +84,7 @@ public class SSHTest { final EchoClientHandler echoClientHandler = connectClient(addr); - Stopwatch stopwatch = new Stopwatch().start(); + Stopwatch stopwatch = Stopwatch.createStarted(); while(echoClientHandler.isConnected() == false && stopwatch.elapsed(TimeUnit.SECONDS) < 30) { Thread.sleep(500); } @@ -131,7 +131,7 @@ public class SSHTest { public void testClientWithoutServer() throws Exception { final InetSocketAddress address = new InetSocketAddress(12345); final EchoClientHandler echoClientHandler = connectClient(address); - final Stopwatch stopwatch = new Stopwatch().start(); + final Stopwatch stopwatch = Stopwatch.createStarted(); while(echoClientHandler.getState() == State.CONNECTING && stopwatch.elapsed(TimeUnit.SECONDS) < 5) { Thread.sleep(100); } diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/FakeModuleBuilderCapability.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/FakeModuleBuilderCapability.java new file mode 100644 index 0000000000..3aba6f81a3 --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/FakeModuleBuilderCapability.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.test.tool; + +import com.google.common.base.Optional; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder; + +/** + * Can be passed instead of ModuleBuilderCapability when building capabilities + * in NetconfDeviceSimulator when testing various schema resolution related exceptions. + */ +public class FakeModuleBuilderCapability implements Capability{ + private static final Date NO_REVISION = new Date(0); + private final ModuleBuilder input; + private final Optional content; + + public FakeModuleBuilderCapability(final ModuleBuilder input, final String inputStream) { + this.input = input; + this.content = Optional.of(inputStream); + } + + @Override + public String getCapabilityUri() { + // FIXME capabilities in Netconf-impl need to check for NO REVISION + final String withoutRevision = getModuleNamespace().get() + "?module=" + getModuleName().get(); + return hasRevision() ? withoutRevision + "&revision=" + Util.writeDate(input.getRevision()) : withoutRevision; + } + + @Override + public Optional getModuleNamespace() { + return Optional.of(input.getNamespace().toString()); + } + + @Override + public Optional getModuleName() { + return Optional.of(input.getName()); + } + + @Override + public Optional getRevision() { + return Optional.of(hasRevision() ? QName.formattedRevision(input.getRevision()) : ""); + } + + private boolean hasRevision() { + return !input.getRevision().equals(NO_REVISION); + } + + /** + * + * @return empty schema source to trigger schema resolution exception. + */ + @Override + public Optional getCapabilitySchema() { + return Optional.absent(); + } + + @Override + public List getLocation() { + return Collections.emptyList(); + } +} diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/Main.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/Main.java index e52fce7ae0..e4cc80fa74 100644 --- a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/Main.java +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/Main.java @@ -78,6 +78,9 @@ public final class Main { @Arg(dest = "debug") public boolean debug; + @Arg(dest = "notification-file") + public File notificationFile; + static ArgumentParser getParser() { final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testool"); @@ -95,6 +98,11 @@ public final class Main { .help("Directory containing yang schemas to describe simulated devices. Some schemas e.g. netconf monitoring and inet types are included by default") .dest("schemas-dir"); + parser.addArgument("--notification-file") + .type(File.class) + .help("Xml file containing notifications that should be sent to clients after create subscription is called") + .dest("notification-file"); + parser.addArgument("--starting-port") .type(Integer.class) .setDefault(17830) diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/ModuleBuilderCapability.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/ModuleBuilderCapability.java index 68f8796d81..11af568321 100644 --- a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/ModuleBuilderCapability.java +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/ModuleBuilderCapability.java @@ -12,8 +12,8 @@ import com.google.common.base.Optional; import java.util.Collections; import java.util.Date; import java.util.List; +import org.opendaylight.controller.netconf.api.Capability; import org.opendaylight.controller.netconf.confignetconfconnector.util.Util; -import org.opendaylight.controller.netconf.mapping.api.Capability; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.parser.builder.impl.ModuleBuilder; diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/NetconfDeviceSimulator.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/NetconfDeviceSimulator.java index 287ff2dca7..bb67af2032 100644 --- a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/NetconfDeviceSimulator.java +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/NetconfDeviceSimulator.java @@ -10,7 +10,7 @@ package org.opendaylight.controller.netconf.test.tool; import com.google.common.base.Charsets; import com.google.common.base.Function; -import com.google.common.base.Objects; +import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; @@ -26,6 +26,7 @@ import io.netty.channel.local.LocalAddress; 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.io.InputStreamReader; @@ -54,23 +55,32 @@ import org.apache.sshd.common.util.ThreadUtils; import org.apache.sshd.server.PasswordAuthenticator; import org.apache.sshd.server.keyprovider.PEMGeneratorHostKeyProvider; import org.apache.sshd.server.session.ServerSession; -import org.opendaylight.controller.netconf.api.monitoring.NetconfManagementSession; +import org.opendaylight.controller.netconf.api.Capability; +import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener; +import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer; -import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher; +import org.opendaylight.controller.netconf.impl.NetconfServerDispatcherImpl; import org.opendaylight.controller.netconf.impl.NetconfServerSessionNegotiatorFactory; import org.opendaylight.controller.netconf.impl.SessionIdProvider; +import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory; import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl; -import org.opendaylight.controller.netconf.impl.osgi.SessionMonitoringService; -import org.opendaylight.controller.netconf.mapping.api.Capability; import org.opendaylight.controller.netconf.mapping.api.NetconfOperation; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider; import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService; -import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceSnapshot; +import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory; +import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringActivator; import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringOperationService; import org.opendaylight.controller.netconf.ssh.SshProxyServer; import org.opendaylight.controller.netconf.ssh.SshProxyServerConfiguration; import org.opendaylight.controller.netconf.ssh.SshProxyServerConfigurationBuilder; +import org.opendaylight.controller.netconf.test.tool.rpc.DataList; +import org.opendaylight.controller.netconf.test.tool.rpc.SimulatedCommit; +import org.opendaylight.controller.netconf.test.tool.rpc.SimulatedCreateSubscription; +import org.opendaylight.controller.netconf.test.tool.rpc.SimulatedEditConfig; +import org.opendaylight.controller.netconf.test.tool.rpc.SimulatedGet; +import org.opendaylight.controller.netconf.test.tool.rpc.SimulatedGetConfig; +import org.opendaylight.controller.netconf.test.tool.rpc.SimulatedLock; +import org.opendaylight.controller.netconf.test.tool.rpc.SimulatedUnLock; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException; import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation; @@ -100,6 +110,8 @@ public class NetconfDeviceSimulator implements Closeable { private final ScheduledExecutorService minaTimerExecutor; private final ExecutorService nioExecutor; + private boolean sendFakeSchema = false; + public NetconfDeviceSimulator() { // TODO make pool size configurable this(new NioEventLoopGroup(), new HashedWheelTimer(), @@ -114,20 +126,31 @@ public class NetconfDeviceSimulator implements Closeable { this.nioExecutor = nioExecutor; } - private NetconfServerDispatcher createDispatcher(final Map moduleBuilders, final boolean exi, final int generateConfigsTimeout) { + private NetconfServerDispatcherImpl createDispatcher(final Map moduleBuilders, final boolean exi, final int generateConfigsTimeout, final Optional notificationsFile) { final Set capabilities = Sets.newHashSet(Collections2.transform(moduleBuilders.keySet(), new Function() { @Override public Capability apply(final ModuleBuilder input) { - return new ModuleBuilderCapability(input, moduleBuilders.get(input)); + if (sendFakeSchema) { + sendFakeSchema = false; + return new FakeModuleBuilderCapability(input, moduleBuilders.get(input)); + } else { + return new ModuleBuilderCapability(input, moduleBuilders.get(input)); + } } })); final SessionIdProvider idProvider = new SessionIdProvider(); - final SimulatedOperationProvider simulatedOperationProvider = new SimulatedOperationProvider(idProvider, capabilities); - final NetconfMonitoringOperationService monitoringService = new NetconfMonitoringOperationService(new NetconfMonitoringServiceImpl(simulatedOperationProvider)); - simulatedOperationProvider.addService(monitoringService); + + final AggregatedNetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = new AggregatedNetconfOperationServiceFactory(); + final SimulatedOperationProvider simulatedOperationProvider = new SimulatedOperationProvider(idProvider, capabilities, notificationsFile); + + final NetconfMonitoringService monitoringService1 = new NetconfMonitoringServiceImpl(aggregatedNetconfOperationServiceFactory); + final NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory monitoringService = + new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory(new NetconfMonitoringOperationService(monitoringService1)); + aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(simulatedOperationProvider); + aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(monitoringService); final DefaultCommitNotificationProducer commitNotifier = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer()); @@ -136,11 +159,11 @@ public class NetconfDeviceSimulator implements Closeable { : Sets.newHashSet(XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0, XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1); final NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory( - hashedWheelTimer, simulatedOperationProvider, idProvider, generateConfigsTimeout, commitNotifier, new LoggingMonitoringService(), serverCapabilities); + hashedWheelTimer, aggregatedNetconfOperationServiceFactory, idProvider, generateConfigsTimeout, commitNotifier, monitoringService1, serverCapabilities); - final NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer( + final NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer( serverNegotiatorFactory); - return new NetconfServerDispatcher(serverChannelInitializer, nettyThreadgroup, nettyThreadgroup); + return new NetconfServerDispatcherImpl(serverChannelInitializer, nettyThreadgroup, nettyThreadgroup); } private Map toModuleBuilders(final Map> sources) { @@ -176,7 +199,7 @@ public class NetconfDeviceSimulator implements Closeable { final Map moduleBuilders = parseSchemasToModuleBuilders(params); - final NetconfServerDispatcher dispatcher = createDispatcher(moduleBuilders, params.exi, params.generateConfigsTimeout); + final NetconfServerDispatcherImpl dispatcher = createDispatcher(moduleBuilders, params.exi, params.generateConfigsTimeout, Optional.fromNullable(params.notificationFile)); int currentPort = params.startingPort; @@ -344,7 +367,7 @@ public class NetconfDeviceSimulator implements Closeable { public CheckedFuture getSource(final SourceIdentifier sourceIdentifier) { return Futures.immediateCheckedFuture(new YangTextSchemaSource(sourceId) { @Override - protected Objects.ToStringHelper addToStringAttributes(final Objects.ToStringHelper toStringHelper) { + protected MoreObjects.ToStringHelper addToStringAttributes(final MoreObjects.ToStringHelper toStringHelper) { return toStringHelper; } @@ -380,61 +403,41 @@ public class NetconfDeviceSimulator implements Closeable { // close Everything } - private static class SimulatedOperationProvider implements NetconfOperationProvider { - private final SessionIdProvider idProvider; - private final Set netconfOperationServices; + private static class SimulatedOperationProvider implements NetconfOperationServiceFactory { + private final Set caps; + private final SimulatedOperationService simulatedOperationService; - public SimulatedOperationProvider(final SessionIdProvider idProvider, final Set caps) { - this.idProvider = idProvider; - final SimulatedOperationService simulatedOperationService = new SimulatedOperationService(caps, idProvider.getCurrentSessionId()); - this.netconfOperationServices = Sets.newHashSet(simulatedOperationService); + public SimulatedOperationProvider(final SessionIdProvider idProvider, final Set caps, final Optional notificationsFile) { + this.caps = caps; + simulatedOperationService = new SimulatedOperationService(idProvider.getCurrentSessionId(), notificationsFile); } @Override - public NetconfOperationServiceSnapshot openSnapshot(final String sessionIdForReporting) { - return new SimulatedServiceSnapshot(idProvider, netconfOperationServices); + public Set getCapabilities() { + return caps; } - public void addService(final NetconfOperationService monitoringService) { - netconfOperationServices.add(monitoringService); + @Override + public AutoCloseable registerCapabilityListener(final CapabilityListener listener) { + return new AutoCloseable() { + @Override + public void close() throws Exception {} + }; } - private static class SimulatedServiceSnapshot implements NetconfOperationServiceSnapshot { - private final SessionIdProvider idProvider; - private final Set netconfOperationServices; - - public SimulatedServiceSnapshot(final SessionIdProvider idProvider, final Set netconfOperationServices) { - this.idProvider = idProvider; - this.netconfOperationServices = netconfOperationServices; - } - - @Override - public String getNetconfSessionIdForReporting() { - return String.valueOf(idProvider.getCurrentSessionId()); - } - - @Override - public Set getServices() { - return netconfOperationServices; - } - - @Override - public void close() throws Exception {} + @Override + public NetconfOperationService createService(final String netconfSessionIdForReporting) { + return simulatedOperationService; } static class SimulatedOperationService implements NetconfOperationService { - private final Set capabilities; private final long currentSessionId; + private final Optional notificationsFile; - public SimulatedOperationService(final Set capabilities, final long currentSessionId) { - this.capabilities = capabilities; + public SimulatedOperationService(final long currentSessionId, final Optional notificationsFile) { this.currentSessionId = currentSessionId; - } - - @Override - public Set getCapabilities() { - return capabilities; + this.notificationsFile = notificationsFile; } @Override @@ -444,7 +447,10 @@ public class NetconfDeviceSimulator implements Closeable { final SimulatedEditConfig sEditConfig = new SimulatedEditConfig(String.valueOf(currentSessionId), storage); final SimulatedGetConfig sGetConfig = new SimulatedGetConfig(String.valueOf(currentSessionId), storage); final SimulatedCommit sCommit = new SimulatedCommit(String.valueOf(currentSessionId)); - return Sets.newHashSet(sGet, sGetConfig, sEditConfig, sCommit); + final SimulatedLock sLock = new SimulatedLock(String.valueOf(currentSessionId)); + final SimulatedUnLock sUnlock = new SimulatedUnLock(String.valueOf(currentSessionId)); + final SimulatedCreateSubscription sCreateSubs = new SimulatedCreateSubscription(String.valueOf(currentSessionId), notificationsFile); + return Sets.newHashSet(sGet, sGetConfig, sEditConfig, sCommit, sLock, sUnlock, sCreateSubs); } @Override @@ -454,16 +460,4 @@ public class NetconfDeviceSimulator implements Closeable { } } - private class LoggingMonitoringService implements SessionMonitoringService { - @Override - public void onSessionUp(final NetconfManagementSession session) { - LOG.debug("Session {} established", session); - } - - @Override - public void onSessionDown(final NetconfManagementSession session) { - LOG.debug("Session {} down", session); - } - } - } diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/DataList.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/DataList.java similarity index 93% rename from opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/DataList.java rename to opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/DataList.java index d6dd55cb4e..abc954e807 100644 --- a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/DataList.java +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/DataList.java @@ -6,7 +6,7 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.test.tool; +package org.opendaylight.controller.netconf.test.tool.rpc; import java.util.Collections; import java.util.List; diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedCommit.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedCommit.java similarity index 85% rename from opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedCommit.java rename to opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedCommit.java index db3717fab4..cb91319699 100644 --- a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedCommit.java +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedCommit.java @@ -6,7 +6,7 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.test.tool; +package org.opendaylight.controller.netconf.test.tool.rpc; import com.google.common.base.Optional; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; @@ -17,9 +17,9 @@ import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; -class SimulatedCommit extends AbstractConfigNetconfOperation { +public class SimulatedCommit extends AbstractConfigNetconfOperation { - SimulatedCommit(final String netconfSessionIdForReporting) { + public SimulatedCommit(final String netconfSessionIdForReporting) { super(null, netconfSessionIdForReporting); } diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedCreateSubscription.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedCreateSubscription.java new file mode 100644 index 0000000000..abf51c4764 --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedCreateSubscription.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.test.tool.rpc; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlRootElement; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.NetconfMessage; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.impl.NetconfServerSession; +import org.opendaylight.controller.netconf.impl.mapping.operations.DefaultNetconfOperation; +import org.opendaylight.controller.netconf.util.mapping.AbstractLastNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +public class SimulatedCreateSubscription extends AbstractLastNetconfOperation implements DefaultNetconfOperation { + + private NetconfServerSession session; + private final Optional notifications; + private ScheduledExecutorService scheduledExecutorService; + + public SimulatedCreateSubscription(final String id, final Optional notificationsFile) { + super(id); + if(notificationsFile.isPresent()) { + notifications = Optional.of(loadNotifications(notificationsFile.get())); + scheduledExecutorService = Executors.newScheduledThreadPool(1); + } else { + notifications = Optional.absent(); + } + } + + private Notifications loadNotifications(final File file) { + try { + final JAXBContext jaxbContext = JAXBContext.newInstance(Notifications.class); + final Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); + return (Notifications) jaxbUnmarshaller.unmarshal(file); + } catch (final JAXBException e) { + throw new IllegalArgumentException("Canot parse file " + file + " as a notifications file", e); + } + } + + @Override + protected String getOperationName() { + return "create-subscription"; + } + + @Override + protected String getOperationNamespace() { + return "urn:ietf:params:xml:ns:netconf:notification:1.0"; + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + + + if(notifications.isPresent()) { + long delayAggregator = 0; + System.console().writer().println("Scheduling notifications " + notifications.get()); + + for (final Notification notification : notifications.get().getNotificationList()) { + for (int i = 0; i <= notification.getTimes(); i++) { + + delayAggregator += notification.getDelayInSeconds(); + + System.console().writer().println("Times " + notification.getTimes()); + scheduledExecutorService.schedule(new Runnable() { + @Override + public void run() { + try { + System.console().writer().println("Sending actual notification " + notification); + Preconditions.checkState(session != null, "Session is not set, cannot process notifications"); + session.sendMessage(parseNetconfNotification(notification.getContent())); + } catch (IOException | SAXException e) { + throw new IllegalStateException("Unable to process notification " + notification, e); + } + } + }, delayAggregator, TimeUnit.SECONDS); + } + } + } + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + private static NetconfMessage parseNetconfNotification(String content) throws IOException, SAXException { + final int startEventTime = content.indexOf("") + "".length(); + final int endEventTime = content.indexOf(""); + final String eventTime = content.substring(startEventTime, endEventTime); + if(eventTime.equals("XXXX")) { + content = content.replace(eventTime, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(new Date())); + } + + return new NetconfMessage(XmlUtil.readXmlToDocument(content)); + } + + @Override + public void setNetconfSession(final NetconfServerSession s) { + this.session = s; + } + + @XmlRootElement(name = "notifications") + public static final class Notifications { + + @javax.xml.bind.annotation.XmlElement(nillable = false, name = "notification", required = true) + private List notificationList; + + public List getNotificationList() { + return notificationList; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Notifications{"); + sb.append("notificationList=").append(notificationList); + sb.append('}'); + return sb.toString(); + } + } + + public static final class Notification { + + @javax.xml.bind.annotation.XmlElement(nillable = false, name = "delay") + private long delayInSeconds; + + @javax.xml.bind.annotation.XmlElement(nillable = false, name = "times") + private long times; + + @javax.xml.bind.annotation.XmlElement(nillable = false, name = "content", required = true) + private String content; + + public long getDelayInSeconds() { + return delayInSeconds; + } + + public long getTimes() { + return times; + } + + public String getContent() { + return content; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Notification{"); + sb.append("delayInSeconds=").append(delayInSeconds); + sb.append(", times=").append(times); + sb.append(", content='").append(content).append('\''); + sb.append('}'); + return sb.toString(); + } + } +} diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedEditConfig.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedEditConfig.java similarity index 91% rename from opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedEditConfig.java rename to opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedEditConfig.java index fd4cd136fd..9ba73f4e3a 100644 --- a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedEditConfig.java +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedEditConfig.java @@ -6,7 +6,7 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.test.tool; +package org.opendaylight.controller.netconf.test.tool.rpc; import com.google.common.base.Optional; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; @@ -19,13 +19,13 @@ import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; -class SimulatedEditConfig extends AbstractConfigNetconfOperation { +public class SimulatedEditConfig extends AbstractConfigNetconfOperation { private static final String DELETE_EDIT_CONFIG = "delete"; private static final String OPERATION = "operation"; private static final String REMOVE_EDIT_CONFIG = "remove"; private final DataList storage; - SimulatedEditConfig(final String netconfSessionIdForReporting, final DataList storage) { + public SimulatedEditConfig(final String netconfSessionIdForReporting, final DataList storage) { super(null, netconfSessionIdForReporting); this.storage = storage; } diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedGet.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedGet.java similarity index 87% rename from opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedGet.java rename to opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedGet.java index 1c24213ca7..24d2fcc35e 100644 --- a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedGet.java +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedGet.java @@ -6,7 +6,7 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.test.tool; +package org.opendaylight.controller.netconf.test.tool.rpc; import com.google.common.base.Optional; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; @@ -17,11 +17,11 @@ import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; -class SimulatedGet extends AbstractConfigNetconfOperation { +public class SimulatedGet extends AbstractConfigNetconfOperation { private final DataList storage; - SimulatedGet(final String netconfSessionIdForReporting, final DataList storage) { + public SimulatedGet(final String netconfSessionIdForReporting, final DataList storage) { super(null, netconfSessionIdForReporting); this.storage = storage; } diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedGetConfig.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedGetConfig.java similarity index 87% rename from opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedGetConfig.java rename to opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedGetConfig.java index cc1258eee4..985cb4015e 100644 --- a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/SimulatedGetConfig.java +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedGetConfig.java @@ -6,7 +6,7 @@ * and is available at http://www.eclipse.org/legal/epl-v10.html */ -package org.opendaylight.controller.netconf.test.tool; +package org.opendaylight.controller.netconf.test.tool.rpc; import com.google.common.base.Optional; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; @@ -17,11 +17,11 @@ import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; -class SimulatedGetConfig extends AbstractConfigNetconfOperation { +public class SimulatedGetConfig extends AbstractConfigNetconfOperation { private final DataList storage; - SimulatedGetConfig(final String netconfSessionIdForReporting, final DataList storage) { + public SimulatedGetConfig(final String netconfSessionIdForReporting, final DataList storage) { super(null, netconfSessionIdForReporting); this.storage = storage; } diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedLock.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedLock.java new file mode 100644 index 0000000000..71b77a0825 --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedLock.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.test.tool.rpc; + +import com.google.common.base.Optional; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class SimulatedLock extends AbstractConfigNetconfOperation { + + public SimulatedLock(final String netconfSessionIdForReporting) { + super(null, netconfSessionIdForReporting); + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + @Override + protected String getOperationName() { + return "lock"; + } +} diff --git a/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedUnLock.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedUnLock.java new file mode 100644 index 0000000000..9d970b7bb5 --- /dev/null +++ b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/rpc/SimulatedUnLock.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.netconf.test.tool.rpc; + +import com.google.common.base.Optional; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; +import org.opendaylight.controller.netconf.confignetconfconnector.operations.AbstractConfigNetconfOperation; +import org.opendaylight.controller.netconf.util.xml.XmlElement; +import org.opendaylight.controller.netconf.util.xml.XmlUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class SimulatedUnLock extends AbstractConfigNetconfOperation { + + public SimulatedUnLock(final String netconfSessionIdForReporting) { + super(null, netconfSessionIdForReporting); + } + + @Override + protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws NetconfDocumentedException { + return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent()); + } + + @Override + protected String getOperationName() { + return "unlock"; + } +} diff --git a/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlFileLoader.java b/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlFileLoader.java index e1331b1380..176a0de9a4 100644 --- a/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlFileLoader.java +++ b/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlFileLoader.java @@ -10,11 +10,9 @@ package org.opendaylight.controller.netconf.util.test; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; -import com.google.common.io.CharStreams; -import com.google.common.io.InputSupplier; +import com.google.common.io.ByteSource; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import javax.xml.parsers.ParserConfigurationException; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.util.xml.XmlUtil; @@ -51,17 +49,13 @@ public class XmlFileLoader { public static String fileToString(final String fileName) throws IOException { try (InputStream resourceAsStream = XmlFileLoader.class.getClassLoader().getResourceAsStream(fileName)) { Preconditions.checkNotNull(resourceAsStream); - - InputSupplier supplier = new InputSupplier() { + return new ByteSource() { @Override - public InputStream getInput() throws IOException { + public InputStream openStream() { return resourceAsStream; } - }; - - InputSupplier readerSupplier = CharStreams.newReaderSupplier(supplier, Charsets.UTF_8); + }.asCharSource(Charsets.UTF_8).read(); - return CharStreams.toString(readerSupplier); } } diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index e1aa6ce3ed..a990b5c6cb 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -13,9 +13,6 @@ 0.3.0-SNAPSHOT pom ${project.artifactId} - - 3.0.4 - netconf-api @@ -23,6 +20,8 @@ netconf-config netconf-impl config-netconf-connector + mdsal-netconf-connector + mdsal-netconf-monitoring netconf-util netconf-netty-util config-persister-impl @@ -31,12 +30,17 @@ netconf-ssh netconf-tcp netconf-monitoring + ietf-netconf ietf-netconf-monitoring + ietf-netconf-notifications ietf-netconf-monitoring-extension netconf-connector-config + netconf-mdsal-config netconf-auth netconf-usermanager netconf-testtool + netconf-notifications-impl + netconf-notifications-api netconf-artifacts diff --git a/features/neutron/pom.xml b/opendaylight/networkconfiguration/neutron/features/pom.xml similarity index 91% rename from features/neutron/pom.xml rename to opendaylight/networkconfiguration/neutron/features/pom.xml index 642ce98351..cd7dce53d4 100644 --- a/features/neutron/pom.xml +++ b/opendaylight/networkconfiguration/neutron/features/pom.xml @@ -16,6 +16,17 @@ + + org.opendaylight.aaa + features-aaa + ${aaa.version} + features + xml + + + org.opendaylight.controller + networkconfig.neutron + org.osgi org.osgi.core diff --git a/features/neutron/src/main/resources/features.xml b/opendaylight/networkconfiguration/neutron/features/src/main/resources/features.xml similarity index 88% rename from features/neutron/src/main/resources/features.xml rename to opendaylight/networkconfiguration/neutron/features/src/main/resources/features.xml index 263f907cee..b244d4d2a9 100644 --- a/features/neutron/src/main/resources/features.xml +++ b/opendaylight/networkconfiguration/neutron/features/src/main/resources/features.xml @@ -3,15 +3,19 @@ + mvn:org.opendaylight.aaa/features-aaa/${aaa.version}/xml/features odl-neutron-api odl-neutron-northbound odl-neutron-implementation + odl-aaa-authn + war odl-neutron-api mvn:org.eclipse.persistence/org.eclipse.persistence.moxy/${eclipse.persistence.version} mvn:org.eclipse.persistence/org.eclipse.persistence.core/${eclipse.persistence.version} + mvn:org.eclipse.persistence/org.eclipse.persistence.antlr/${eclipse.persistence.version} mvn:org.opendaylight.controller/networkconfig.neutron.northbound/${networkconfig.neutron.northbound.version} mvn:com.sun.jersey/jersey-core/${jersey.version} mvn:com.sun.jersey/jersey-server/${jersey.version} @@ -26,7 +30,8 @@ odl-neutron-api + war mvn:org.opendaylight.controller/networkconfig.neutron.implementation/${networkconfig.neutron.implementation.version} mvn:org.osgi/org.osgi.core/${osgi.core.version} - \ No newline at end of file + diff --git a/opendaylight/networkconfiguration/neutron/implementation/pom.xml b/opendaylight/networkconfiguration/neutron/implementation/pom.xml index 1607408e7a..9eaebb283c 100644 --- a/opendaylight/networkconfiguration/neutron/implementation/pom.xml +++ b/opendaylight/networkconfiguration/neutron/implementation/pom.xml @@ -10,9 +10,6 @@ networkconfig.neutron.implementation 0.5.0-SNAPSHOT bundle - - 1.26.2 - org.opendaylight.controller diff --git a/opendaylight/networkconfiguration/neutron/northbound/pom.xml b/opendaylight/networkconfiguration/neutron/northbound/pom.xml index 0eecca0c3e..1e7bcf0ae6 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/pom.xml +++ b/opendaylight/networkconfiguration/neutron/northbound/pom.xml @@ -3,9 +3,9 @@ 4.0.0 org.opendaylight.controller - commons.opendaylight + enunciate-parent 1.5.0-SNAPSHOT - ../../../commons/opendaylight + ../../../commons/enunciate-parent networkconfig.neutron.northbound 0.5.0-SNAPSHOT @@ -35,6 +35,10 @@ org.opendaylight.controller networkconfig.neutron + + org.osgi + org.osgi.core + @@ -61,10 +65,6 @@ ${project.basedir}/src/main/resources/META-INF - - org.codehaus.enunciate - maven-enunciate-plugin - diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallNorthbound.java index 33043d4389..b97a554f60 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallNorthbound.java @@ -187,13 +187,19 @@ public class NeutronFirewallNorthbound { firewallInterface.addNeutronFirewall(singleton); Object[] instances = NeutronUtil.getInstances(INeutronFirewallAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFirewallAware service = (INeutronFirewallAware) instance; - int status = service.canCreateNeutronFirewall(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallAware service = (INeutronFirewallAware) instance; + int status = service.canCreateNeutronFirewall(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } firewallInterface.addNeutronFirewall(singleton); if (instances != null) { @@ -220,13 +226,19 @@ public class NeutronFirewallNorthbound { throw new BadRequestException("Firewall UUID already exists"); } if (instances != null) { - for (Object instance : instances) { - INeutronFirewallAware service = (INeutronFirewallAware) instance; - int status = service.canCreateNeutronFirewall(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallAware service = (INeutronFirewallAware) instance; + int status = service.canCreateNeutronFirewall(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } @@ -298,13 +310,19 @@ public class NeutronFirewallNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronFirewallAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFirewallAware service = (INeutronFirewallAware) instance; - int status = service.canUpdateNeutronFirewall(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallAware service = (INeutronFirewallAware) instance; + int status = service.canUpdateNeutronFirewall(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -352,13 +370,19 @@ public class NeutronFirewallNorthbound { NeutronFirewall singleton = firewallInterface.getNeutronFirewall(firewallUUID); Object[] instances = NeutronUtil.getInstances(INeutronFirewallAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFirewallAware service = (INeutronFirewallAware) instance; - int status = service.canDeleteNeutronFirewall(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallAware service = (INeutronFirewallAware) instance; + int status = service.canDeleteNeutronFirewall(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallPolicyNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallPolicyNorthbound.java index 08e563138c..e9b813d731 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallPolicyNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallPolicyNorthbound.java @@ -184,13 +184,19 @@ public class NeutronFirewallPolicyNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronFirewallPolicyAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFirewallPolicyAware service = (INeutronFirewallPolicyAware) instance; - int status = service.canCreateNeutronFirewallPolicy(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallPolicyAware service = (INeutronFirewallPolicyAware) instance; + int status = service.canCreateNeutronFirewallPolicy(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } firewallPolicyInterface.addNeutronFirewallPolicy(singleton); if (instances != null) { @@ -218,13 +224,19 @@ public class NeutronFirewallPolicyNorthbound { throw new BadRequestException("Firewall Policy UUID already exists"); } if (instances != null) { - for (Object instance : instances) { - INeutronFirewallPolicyAware service = (INeutronFirewallPolicyAware) instance; - int status = service.canCreateNeutronFirewallPolicy(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallPolicyAware service = (INeutronFirewallPolicyAware) instance; + int status = service.canCreateNeutronFirewallPolicy(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } /* @@ -295,13 +307,19 @@ public class NeutronFirewallPolicyNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronFirewallPolicyAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFirewallPolicyAware service = (INeutronFirewallPolicyAware) instance; - int status = service.canUpdateNeutronFirewallPolicy(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallPolicyAware service = (INeutronFirewallPolicyAware) instance; + int status = service.canUpdateNeutronFirewallPolicy(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -349,13 +367,19 @@ public class NeutronFirewallPolicyNorthbound { NeutronFirewallPolicy singleton = firewallPolicyInterface.getNeutronFirewallPolicy(firewallPolicyUUID); Object[] instances = NeutronUtil.getInstances(INeutronFirewallPolicyAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFirewallPolicyAware service = (INeutronFirewallPolicyAware) instance; - int status = service.canDeleteNeutronFirewallPolicy(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallPolicyAware service = (INeutronFirewallPolicyAware) instance; + int status = service.canDeleteNeutronFirewallPolicy(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } firewallPolicyInterface.removeNeutronFirewallPolicy(firewallPolicyUUID); diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallRulesNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallRulesNorthbound.java index 5e51711e5b..40b830da55 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallRulesNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFirewallRulesNorthbound.java @@ -220,13 +220,19 @@ public class NeutronFirewallRulesNorthbound { firewallRuleInterface.addNeutronFirewallRule(singleton); Object[] instances = NeutronUtil.getInstances(INeutronFirewallRuleAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFirewallRuleAware service = (INeutronFirewallRuleAware) instance; - int status = service.canCreateNeutronFirewallRule(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallRuleAware service = (INeutronFirewallRuleAware) instance; + int status = service.canCreateNeutronFirewallRule(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } // add rule to cache singleton.initDefaults(); @@ -256,13 +262,19 @@ public class NeutronFirewallRulesNorthbound { throw new BadRequestException("Firewall Rule UUID already exists"); } if (instances != null) { - for (Object instance : instances) { - INeutronFirewallRuleAware service = (INeutronFirewallRuleAware) instance; - int status = service.canCreateNeutronFirewallRule(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallRuleAware service = (INeutronFirewallRuleAware) instance; + int status = service.canCreateNeutronFirewallRule(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } /* @@ -342,13 +354,19 @@ public class NeutronFirewallRulesNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronFirewallRuleAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFirewallRuleAware service = (INeutronFirewallRuleAware) instance; - int status = service.canUpdateNeutronFirewallRule(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallRuleAware service = (INeutronFirewallRuleAware) instance; + int status = service.canUpdateNeutronFirewallRule(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -399,13 +417,19 @@ public class NeutronFirewallRulesNorthbound { NeutronFirewallRule singleton = firewallRuleInterface.getNeutronFirewallRule(firewallRuleUUID); Object[] instances = NeutronUtil.getInstances(INeutronFirewallRuleAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFirewallRuleAware service = (INeutronFirewallRuleAware) instance; - int status = service.canDeleteNeutronFirewallRule(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFirewallRuleAware service = (INeutronFirewallRuleAware) instance; + int status = service.canDeleteNeutronFirewallRule(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFloatingIPsNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFloatingIPsNorthbound.java index 812a09a0a6..3e6c2a4feb 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFloatingIPsNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronFloatingIPsNorthbound.java @@ -242,12 +242,18 @@ public class NeutronFloatingIPsNorthbound { } Object[] instances = NeutronUtil.getInstances(INeutronFloatingIPAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFloatingIPAware service = (INeutronFloatingIPAware) instance; - int status = service.canCreateFloatingIP(singleton); - if (status < 200 || status > 299) - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFloatingIPAware service = (INeutronFloatingIPAware) instance; + int status = service.canCreateFloatingIP(singleton); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } floatingIPInterface.addFloatingIP(singleton); if (instances != null) { @@ -363,12 +369,18 @@ public class NeutronFloatingIPsNorthbound { NeutronFloatingIP target = floatingIPInterface.getFloatingIP(floatingipUUID); Object[] instances = NeutronUtil.getInstances(INeutronFloatingIPAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFloatingIPAware service = (INeutronFloatingIPAware) instance; - int status = service.canUpdateFloatingIP(singleton, target); - if (status < 200 || status > 299) - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFloatingIPAware service = (INeutronFloatingIPAware) instance; + int status = service.canUpdateFloatingIP(singleton, target); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } floatingIPInterface.updateFloatingIP(floatingipUUID, singleton); target = floatingIPInterface.getFloatingIP(floatingipUUID); @@ -406,12 +418,18 @@ public class NeutronFloatingIPsNorthbound { NeutronFloatingIP singleton = floatingIPInterface.getFloatingIP(floatingipUUID); Object[] instances = NeutronUtil.getInstances(INeutronFloatingIPAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronFloatingIPAware service = (INeutronFloatingIPAware) instance; - int status = service.canDeleteFloatingIP(singleton); - if (status < 200 || status > 299) - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronFloatingIPAware service = (INeutronFloatingIPAware) instance; + int status = service.canDeleteFloatingIP(singleton); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } floatingIPInterface.removeFloatingIP(floatingipUUID); if (instances != null) { diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerHealthMonitorNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerHealthMonitorNorthbound.java index 85aba5d690..aa30e948a2 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerHealthMonitorNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerHealthMonitorNorthbound.java @@ -210,13 +210,19 @@ public class NeutronLoadBalancerHealthMonitorNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerHealthMonitorAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerHealthMonitorAware service = (INeutronLoadBalancerHealthMonitorAware) instance; - int status = service.canCreateNeutronLoadBalancerHealthMonitor(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerHealthMonitorAware service = (INeutronLoadBalancerHealthMonitorAware) instance; + int status = service.canCreateNeutronLoadBalancerHealthMonitor(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } loadBalancerHealthMonitorInterface.addNeutronLoadBalancerHealthMonitor(singleton); if (instances != null) { @@ -245,13 +251,19 @@ public class NeutronLoadBalancerHealthMonitorNorthbound { throw new BadRequestException("LoadBalancerHealthMonitor UUID already exists"); } if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerHealthMonitorAware service = (INeutronLoadBalancerHealthMonitorAware) instance; - int status = service.canCreateNeutronLoadBalancerHealthMonitor(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerHealthMonitorAware service = (INeutronLoadBalancerHealthMonitorAware) instance; + int status = service.canCreateNeutronLoadBalancerHealthMonitor(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } /* @@ -328,13 +340,19 @@ public class NeutronLoadBalancerHealthMonitorNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerHealthMonitorAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerHealthMonitorAware service = (INeutronLoadBalancerHealthMonitorAware) instance; - int status = service.canUpdateNeutronLoadBalancerHealthMonitor(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerHealthMonitorAware service = (INeutronLoadBalancerHealthMonitorAware) instance; + int status = service.canUpdateNeutronLoadBalancerHealthMonitor(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -386,13 +404,19 @@ public class NeutronLoadBalancerHealthMonitorNorthbound { NeutronLoadBalancerHealthMonitor singleton = loadBalancerHealthMonitorInterface.getNeutronLoadBalancerHealthMonitor(loadBalancerHealthMonitorID); Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerHealthMonitorAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerHealthMonitorAware service = (INeutronLoadBalancerHealthMonitorAware) instance; - int status = service.canDeleteNeutronLoadBalancerHealthMonitor(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerHealthMonitorAware service = (INeutronLoadBalancerHealthMonitorAware) instance; + int status = service.canDeleteNeutronLoadBalancerHealthMonitor(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } loadBalancerHealthMonitorInterface.removeNeutronLoadBalancerHealthMonitor(loadBalancerHealthMonitorID); if (instances != null) { diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerListenerNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerListenerNorthbound.java index 345c7ee0f3..5d877c59f4 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerListenerNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerListenerNorthbound.java @@ -198,13 +198,19 @@ public class NeutronLoadBalancerListenerNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerListenerAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerListenerAware service = (INeutronLoadBalancerListenerAware) instance; - int status = service.canCreateNeutronLoadBalancerListener(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerListenerAware service = (INeutronLoadBalancerListenerAware) instance; + int status = service.canCreateNeutronLoadBalancerListener(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } loadBalancerListenerInterface.addNeutronLoadBalancerListener(singleton); if (instances != null) { @@ -232,13 +238,19 @@ public class NeutronLoadBalancerListenerNorthbound { throw new BadRequestException("LoadBalancerListener UUID already exists"); } if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerListenerAware service = (INeutronLoadBalancerListenerAware) instance; - int status = service.canCreateNeutronLoadBalancerListener(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerListenerAware service = (INeutronLoadBalancerListenerAware) instance; + int status = service.canCreateNeutronLoadBalancerListener(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } /* @@ -312,13 +324,19 @@ public class NeutronLoadBalancerListenerNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerListenerAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerListenerAware service = (INeutronLoadBalancerListenerAware) instance; - int status = service.canUpdateNeutronLoadBalancerListener(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerListenerAware service = (INeutronLoadBalancerListenerAware) instance; + int status = service.canUpdateNeutronLoadBalancerListener(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -366,13 +384,19 @@ public class NeutronLoadBalancerListenerNorthbound { NeutronLoadBalancerListener singleton = loadBalancerListenerInterface.getNeutronLoadBalancerListener(loadBalancerListenerID); Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerListenerAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerListenerAware service = (INeutronLoadBalancerListenerAware) instance; - int status = service.canDeleteNeutronLoadBalancerListener(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerListenerAware service = (INeutronLoadBalancerListenerAware) instance; + int status = service.canDeleteNeutronLoadBalancerListener(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } loadBalancerListenerInterface.removeNeutronLoadBalancerListener(loadBalancerListenerID); diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerNorthbound.java index 308041fe59..67557ce41f 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerNorthbound.java @@ -186,14 +186,21 @@ public class NeutronLoadBalancerNorthbound { } Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerAware service = (INeutronLoadBalancerAware) instance; - int status = service.canCreateNeutronLoadBalancer(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerAware service = (INeutronLoadBalancerAware) instance; + int status = service.canCreateNeutronLoadBalancer(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } + loadBalancerInterface.addNeutronLoadBalancer(singleton); if (instances != null) { for (Object instance : instances) { @@ -220,13 +227,19 @@ public class NeutronLoadBalancerNorthbound { throw new BadRequestException("Load Balancer Pool UUID already exists"); } if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerAware service = (INeutronLoadBalancerAware) instance; - int status = service.canCreateNeutronLoadBalancer(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerAware service = (INeutronLoadBalancerAware) instance; + int status = service.canCreateNeutronLoadBalancer(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } /* @@ -298,13 +311,19 @@ public class NeutronLoadBalancerNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerAware service = (INeutronLoadBalancerAware) instance; - int status = service.canUpdateNeutronLoadBalancer(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerAware service = (INeutronLoadBalancerAware) instance; + int status = service.canUpdateNeutronLoadBalancer(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -355,15 +374,22 @@ public class NeutronLoadBalancerNorthbound { NeutronLoadBalancer singleton = loadBalancerInterface.getNeutronLoadBalancer(loadBalancerID); Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerAware service = (INeutronLoadBalancerAware) instance; - int status = service.canDeleteNeutronLoadBalancer(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerAware service = (INeutronLoadBalancerAware) instance; + int status = service.canDeleteNeutronLoadBalancer(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } + loadBalancerInterface.removeNeutronLoadBalancer(loadBalancerID); if (instances != null) { for (Object instance : instances) { diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerPoolMembersNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerPoolMembersNorthbound.java index f9540679cd..22d118ae79 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerPoolMembersNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerPoolMembersNorthbound.java @@ -200,14 +200,21 @@ public Response createLoadBalancerPoolMember( Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerPoolMemberAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerPoolMemberAware service = (INeutronLoadBalancerPoolMemberAware) instance; - int status = service.canCreateNeutronLoadBalancerPoolMember(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerPoolMemberAware service = (INeutronLoadBalancerPoolMemberAware) instance; + int status = service.canCreateNeutronLoadBalancerPoolMember(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } + if (instances != null) { for (Object instance : instances) { INeutronLoadBalancerPoolMemberAware service = (INeutronLoadBalancerPoolMemberAware) instance; @@ -242,13 +249,19 @@ public Response createLoadBalancerPoolMember( throw new BadRequestException("Load Balancer PoolMember UUID already exists"); } if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerPoolMemberAware service = (INeutronLoadBalancerPoolMemberAware) instance; - int status = service.canCreateNeutronLoadBalancerPoolMember(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerPoolMemberAware service = (INeutronLoadBalancerPoolMemberAware) instance; + int status = service.canCreateNeutronLoadBalancerPoolMember(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } /* @@ -334,13 +347,19 @@ public Response deleteLoadBalancerPoolMember( Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerPoolMemberAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerPoolMemberAware service = (INeutronLoadBalancerPoolMemberAware) instance; - int status = service.canDeleteNeutronLoadBalancerPoolMember(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerPoolMemberAware service = (INeutronLoadBalancerPoolMemberAware) instance; + int status = service.canDeleteNeutronLoadBalancerPoolMember(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } if (instances != null) { diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerPoolNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerPoolNorthbound.java index 77bb525c84..ea4e2d1c9a 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerPoolNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronLoadBalancerPoolNorthbound.java @@ -198,13 +198,19 @@ public class NeutronLoadBalancerPoolNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerPoolAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerPoolAware service = (INeutronLoadBalancerPoolAware) instance; - int status = service.canCreateNeutronLoadBalancerPool(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerPoolAware service = (INeutronLoadBalancerPoolAware) instance; + int status = service.canCreateNeutronLoadBalancerPool(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } loadBalancerPoolInterface.addNeutronLoadBalancerPool(singleton); if (instances != null) { @@ -232,13 +238,19 @@ public class NeutronLoadBalancerPoolNorthbound { throw new BadRequestException("Load Balancer Pool UUID already exists"); } if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerPoolAware service = (INeutronLoadBalancerPoolAware) instance; - int status = service.canCreateNeutronLoadBalancerPool(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerPoolAware service = (INeutronLoadBalancerPoolAware) instance; + int status = service.canCreateNeutronLoadBalancerPool(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } /* @@ -311,13 +323,19 @@ public class NeutronLoadBalancerPoolNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerPoolAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerPoolAware service = (INeutronLoadBalancerPoolAware) instance; - int status = service.canUpdateNeutronLoadBalancerPool(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerPoolAware service = (INeutronLoadBalancerPoolAware) instance; + int status = service.canUpdateNeutronLoadBalancerPool(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -366,13 +384,19 @@ public class NeutronLoadBalancerPoolNorthbound { NeutronLoadBalancerPool singleton = loadBalancerPoolInterface.getNeutronLoadBalancerPool(loadBalancerPoolUUID); Object[] instances = NeutronUtil.getInstances(INeutronLoadBalancerPoolAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronLoadBalancerPoolAware service = (INeutronLoadBalancerPoolAware) instance; - int status = service.canDeleteNeutronLoadBalancerPool(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronLoadBalancerPoolAware service = (INeutronLoadBalancerPoolAware) instance; + int status = service.canDeleteNeutronLoadBalancerPool(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronNetworksNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronNetworksNorthbound.java index 9c99f346e8..3a3c657956 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronNetworksNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronNetworksNorthbound.java @@ -204,13 +204,19 @@ public class NeutronNetworksNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronNetworkAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronNetworkAware service = (INeutronNetworkAware) instance; - int status = service.canCreateNetwork(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronNetworkAware service = (INeutronNetworkAware) instance; + int status = service.canCreateNetwork(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } // add network to cache @@ -242,13 +248,19 @@ public class NeutronNetworksNorthbound { throw new BadRequestException("network UUID already exists"); } if (instances != null) { - for (Object instance: instances) { - INeutronNetworkAware service = (INeutronNetworkAware) instance; - int status = service.canCreateNetwork(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance: instances) { + INeutronNetworkAware service = (INeutronNetworkAware) instance; + int status = service.canCreateNetwork(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } testMap.put(test.getID(),test); } @@ -312,14 +324,20 @@ public class NeutronNetworksNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronNetworkAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronNetworkAware service = (INeutronNetworkAware) instance; - NeutronNetwork original = networkInterface.getNetwork(netUUID); - int status = service.canUpdateNetwork(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronNetworkAware service = (INeutronNetworkAware) instance; + NeutronNetwork original = networkInterface.getNetwork(netUUID); + int status = service.canUpdateNetwork(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } // update network object and return the modified object @@ -366,14 +384,21 @@ public class NeutronNetworksNorthbound { NeutronNetwork singleton = networkInterface.getNetwork(netUUID); Object[] instances = NeutronUtil.getInstances(INeutronNetworkAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronNetworkAware service = (INeutronNetworkAware) instance; - int status = service.canDeleteNetwork(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronNetworkAware service = (INeutronNetworkAware) instance; + int status = service.canDeleteNetwork(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } + networkInterface.removeNetwork(netUUID); if (instances != null) { for (Object instance : instances) { diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronNorthboundRSApplication.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronNorthboundRSApplication.java index 96d72cb926..052d3dc2dd 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronNorthboundRSApplication.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronNorthboundRSApplication.java @@ -43,6 +43,7 @@ public class NeutronNorthboundRSApplication extends Application { classes.add(NeutronLoadBalancerPoolNorthbound.class); classes.add(NeutronLoadBalancerHealthMonitorNorthbound.class); classes.add(NeutronLoadBalancerPoolMembersNorthbound.class); + classes.add(MOXyJsonProvider.class); return classes; } @@ -56,9 +57,10 @@ public class NeutronNorthboundRSApplication extends Application { moxyJsonProvider.setMarshalEmptyCollections(true); moxyJsonProvider.setValueWrapper("$"); - Map namespacePrefixMapper = new HashMap(1); + Map namespacePrefixMapper = new HashMap(3); namespacePrefixMapper.put("router", "router"); // FIXME: fill in with XSD namespacePrefixMapper.put("provider", "provider"); // FIXME: fill in with XSD + namespacePrefixMapper.put("binding", "binding"); moxyJsonProvider.setNamespacePrefixMapper(namespacePrefixMapper); moxyJsonProvider.setNamespaceSeparator(':'); diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronPortsNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronPortsNorthbound.java index 23f4979f23..5ff3de58d6 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronPortsNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronPortsNorthbound.java @@ -259,16 +259,21 @@ public class NeutronPortsNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronPortAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronPortAware service = (INeutronPortAware) instance; - int status = service.canCreatePort(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronPortAware service = (INeutronPortAware) instance; + int status = service.canCreatePort(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } - // add the port to the cache portInterface.addPort(singleton); if (instances != null) { @@ -353,13 +358,19 @@ public class NeutronPortsNorthbound { } } if (instances != null) { - for (Object instance : instances) { - INeutronPortAware service = (INeutronPortAware) instance; - int status = service.canCreatePort(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronPortAware service = (INeutronPortAware) instance; + int status = service.canCreatePort(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } @@ -429,13 +440,19 @@ public class NeutronPortsNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronPortAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronPortAware service = (INeutronPortAware) instance; - int status = service.canUpdatePort(singleton, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronPortAware service = (INeutronPortAware) instance; + int status = service.canUpdatePort(singleton, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } // Verify the new fixed ips are valid @@ -511,13 +528,19 @@ public class NeutronPortsNorthbound { NeutronPort singleton = portInterface.getPort(portUUID); Object[] instances = NeutronUtil.getInstances(INeutronPortAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronPortAware service = (INeutronPortAware) instance; - int status = service.canDeletePort(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronPortAware service = (INeutronPortAware) instance; + int status = service.canDeletePort(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } portInterface.removePort(portUUID); if (instances != null) { diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronRoutersNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronRoutersNorthbound.java index 45e84f4d15..ccf5ddd1a4 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronRoutersNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronRoutersNorthbound.java @@ -194,12 +194,18 @@ public class NeutronRoutersNorthbound { } Object[] instances = NeutronUtil.getInstances(INeutronRouterAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronRouterAware service = (INeutronRouterAware) instance; - int status = service.canCreateRouter(singleton); - if (status < 200 || status > 299) - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronRouterAware service = (INeutronRouterAware) instance; + int status = service.canCreateRouter(singleton); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -270,12 +276,18 @@ public class NeutronRoutersNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronRouterAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronRouterAware service = (INeutronRouterAware) instance; - int status = service.canUpdateRouter(singleton, original); - if (status < 200 || status > 299) - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronRouterAware service = (INeutronRouterAware) instance; + int status = service.canUpdateRouter(singleton, original); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* * if the external gateway info is being changed, verify that the new network @@ -335,12 +347,18 @@ public class NeutronRoutersNorthbound { NeutronRouter singleton = routerInterface.getRouter(routerUUID); Object[] instances = NeutronUtil.getInstances(INeutronRouterAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronRouterAware service = (INeutronRouterAware) instance; - int status = service.canDeleteRouter(singleton); - if (status < 200 || status > 299) - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronRouterAware service = (INeutronRouterAware) instance; + int status = service.canDeleteRouter(singleton); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } routerInterface.removeRouter(routerUUID); if (instances != null) { @@ -415,12 +433,18 @@ public class NeutronRoutersNorthbound { throw new ResourceConflictException("Target Port already allocated"); Object[] instances = NeutronUtil.getInstances(INeutronRouterAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronRouterAware service = (INeutronRouterAware) instance; - int status = service.canAttachInterface(target, input); - if (status < 200 || status > 299) - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronRouterAware service = (INeutronRouterAware) instance; + int status = service.canAttachInterface(target, input); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } //mark the port device id and device owner fields @@ -493,12 +517,18 @@ public class NeutronRoutersNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronRouterAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronRouterAware service = (INeutronRouterAware) instance; - int status = service.canDetachInterface(target, input); - if (status < 200 || status > 299) - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronRouterAware service = (INeutronRouterAware) instance; + int status = service.canDetachInterface(target, input); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } // reset the port ownership @@ -528,11 +558,25 @@ public class NeutronRoutersNorthbound { input.setSubnetUUID(targetInterface.getSubnetUUID()); input.setID(target.getID()); input.setTenantID(target.getTenantID()); + Object[] instances = NeutronUtil.getInstances(INeutronRouterAware.class, this); + if (instances != null) { + if (instances.length > 0) { + for (Object instance : instances) { + INeutronRouterAware service = (INeutronRouterAware) instance; + int status = service.canDetachInterface(target, input); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); + } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); + } NeutronPort port = portInterface.getPort(input.getPortUUID()); port.setDeviceID(null); port.setDeviceOwner(null); target.removeInterface(input.getPortUUID()); - Object[] instances = NeutronUtil.getInstances(INeutronRouterAware.class, this); for (Object instance : instances) { INeutronRouterAware service = (INeutronRouterAware) instance; service.neutronRouterInterfaceDetached(target, input); @@ -560,19 +604,32 @@ public class NeutronRoutersNorthbound { } if (!subnet.isValidIP(port.getFixedIPs().get(0).getIpAddress())) throw new ResourceConflictException("Target Port IP not in Target Subnet"); - input.setID(target.getID()); - input.setTenantID(target.getTenantID()); Object[] instances = NeutronUtil.getInstances(INeutronRouterAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronRouterAware service = (INeutronRouterAware) instance; - service.canDetachInterface(target, input); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronRouterAware service = (INeutronRouterAware) instance; + int status = service.canDetachInterface(target, input); + if (status < 200 || status > 299) + return Response.status(status).build(); + } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } + input.setID(target.getID()); + input.setTenantID(target.getTenantID()); port.setDeviceID(null); port.setDeviceOwner(null); target.removeInterface(input.getPortUUID()); - for (Object instance : instances) { + if (instances != null) { + for (Object instance : instances) { + INeutronRouterAware service = (INeutronRouterAware) instance; + service.canDetachInterface(target, input); + } + } for (Object instance : instances) { INeutronRouterAware service = (INeutronRouterAware) instance; service.neutronRouterInterfaceDetached(target, input); } diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSecurityGroupsNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSecurityGroupsNorthbound.java index bce4de7cc2..d9ca6d4512 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSecurityGroupsNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSecurityGroupsNorthbound.java @@ -178,13 +178,19 @@ public class NeutronSecurityGroupsNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronSecurityGroupAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronSecurityGroupAware service = (INeutronSecurityGroupAware) instance; - int status = service.canCreateNeutronSecurityGroup(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSecurityGroupAware service = (INeutronSecurityGroupAware) instance; + int status = service.canCreateNeutronSecurityGroup(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } // Add to Neutron cache securityGroupInterface.addNeutronSecurityGroup(singleton); @@ -209,10 +215,18 @@ public class NeutronSecurityGroupsNorthbound { if (securityGroupInterface.neutronSecurityGroupExists(test.getSecurityGroupUUID())) { throw new BadRequestException("Security Group UUID already is already created"); } - if (instances != null) for (Object instance : instances) { - INeutronSecurityGroupAware service = (INeutronSecurityGroupAware) instance; - int status = service.canCreateNeutronSecurityGroup(test); - if ((status < 200) || (status > 299)) return Response.status(status).build(); + if (instances != null) { + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSecurityGroupAware service = (INeutronSecurityGroupAware) instance; + int status = service.canCreateNeutronSecurityGroup(test); + if ((status < 200) || (status > 299)) return Response.status(status).build(); + } + } else { + throw new BadRequestException("No providers registered. Please try again later"); + } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } @@ -278,13 +292,19 @@ public class NeutronSecurityGroupsNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronSecurityGroupAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronSecurityGroupAware service = (INeutronSecurityGroupAware) instance; - int status = service.canUpdateNeutronSecurityGroup(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSecurityGroupAware service = (INeutronSecurityGroupAware) instance; + int status = service.canUpdateNeutronSecurityGroup(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -333,13 +353,19 @@ public class NeutronSecurityGroupsNorthbound { NeutronSecurityGroup singleton = securityGroupInterface.getNeutronSecurityGroup(securityGroupUUID); Object[] instances = NeutronUtil.getInstances(INeutronSecurityGroupAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronSecurityGroupAware service = (INeutronSecurityGroupAware) instance; - int status = service.canDeleteNeutronSecurityGroup(singleton); - if ((status < 200) || (status > 299)) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSecurityGroupAware service = (INeutronSecurityGroupAware) instance; + int status = service.canDeleteNeutronSecurityGroup(singleton); + if ((status < 200) || (status > 299)) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -354,4 +380,4 @@ public class NeutronSecurityGroupsNorthbound { } return Response.status(204).build(); } -} \ No newline at end of file +} diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSecurityRulesNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSecurityRulesNorthbound.java index 762317aaf8..9ce98e2b4f 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSecurityRulesNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSecurityRulesNorthbound.java @@ -201,13 +201,19 @@ public class NeutronSecurityRulesNorthbound { } Object[] instances = NeutronUtil.getInstances(INeutronSecurityRuleAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronSecurityRuleAware service = (INeutronSecurityRuleAware) instance; - int status = service.canCreateNeutronSecurityRule(singleton); - if ((status < 200) || (status > 299)) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSecurityRuleAware service = (INeutronSecurityRuleAware) instance; + int status = service.canCreateNeutronSecurityRule(singleton); + if ((status < 200) || (status > 299)) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } // add rule to cache @@ -246,13 +252,19 @@ public class NeutronSecurityRulesNorthbound { throw new BadRequestException("Security Rule UUID already exists"); } if (instances != null) { - for (Object instance : instances) { - INeutronSecurityRuleAware service = (INeutronSecurityRuleAware) instance; - int status = service.canCreateNeutronSecurityRule(test); - if ((status < 200) || (status > 299)) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSecurityRuleAware service = (INeutronSecurityRuleAware) instance; + int status = service.canCreateNeutronSecurityRule(test); + if ((status < 200) || (status > 299)) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } @@ -328,13 +340,19 @@ public class NeutronSecurityRulesNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronSecurityRuleAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronSecurityRuleAware service = (INeutronSecurityRuleAware) instance; - int status = service.canUpdateNeutronSecurityRule(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSecurityRuleAware service = (INeutronSecurityRuleAware) instance; + int status = service.canUpdateNeutronSecurityRule(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -383,15 +401,22 @@ public class NeutronSecurityRulesNorthbound { NeutronSecurityRule singleton = securityRuleInterface.getNeutronSecurityRule(securityRuleUUID); Object[] instances = NeutronUtil.getInstances(INeutronSecurityRuleAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronSecurityRuleAware service = (INeutronSecurityRuleAware) instance; - int status = service.canDeleteNeutronSecurityRule(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSecurityRuleAware service = (INeutronSecurityRuleAware) instance; + int status = service.canDeleteNeutronSecurityRule(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } + /* * remove it and return 204 status */ @@ -404,4 +429,4 @@ public class NeutronSecurityRulesNorthbound { } return Response.status(204).build(); } -} \ No newline at end of file +} diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSubnetsNorthbound.java b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSubnetsNorthbound.java index 142b09f8e4..fa66501d0e 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSubnetsNorthbound.java +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronSubnetsNorthbound.java @@ -219,13 +219,19 @@ public class NeutronSubnetsNorthbound { } Object[] instances = NeutronUtil.getInstances(INeutronSubnetAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronSubnetAware service = (INeutronSubnetAware) instance; - int status = service.canCreateSubnet(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSubnetAware service = (INeutronSubnetAware) instance; + int status = service.canCreateSubnet(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } subnetInterface.addSubnet(singleton); if (instances != null) { @@ -269,13 +275,19 @@ public class NeutronSubnetsNorthbound { throw new ResourceConflictException("IP pool overlaps with gateway"); } if (instances != null) { - for (Object instance : instances) { - INeutronSubnetAware service = (INeutronSubnetAware) instance; - int status = service.canCreateSubnet(test); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSubnetAware service = (INeutronSubnetAware) instance; + int status = service.canCreateSubnet(test); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } } @@ -344,13 +356,19 @@ public class NeutronSubnetsNorthbound { Object[] instances = NeutronUtil.getInstances(INeutronSubnetAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronSubnetAware service = (INeutronSubnetAware) instance; - int status = service.canUpdateSubnet(delta, original); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSubnetAware service = (INeutronSubnetAware) instance; + int status = service.canUpdateSubnet(delta, original); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* @@ -399,13 +417,19 @@ public class NeutronSubnetsNorthbound { NeutronSubnet singleton = subnetInterface.getSubnet(subnetUUID); Object[] instances = NeutronUtil.getInstances(INeutronSubnetAware.class, this); if (instances != null) { - for (Object instance : instances) { - INeutronSubnetAware service = (INeutronSubnetAware) instance; - int status = service.canDeleteSubnet(singleton); - if (status < 200 || status > 299) { - return Response.status(status).build(); + if (instances.length > 0) { + for (Object instance : instances) { + INeutronSubnetAware service = (INeutronSubnetAware) instance; + int status = service.canDeleteSubnet(singleton); + if (status < 200 || status > 299) { + return Response.status(status).build(); + } } + } else { + throw new ServiceUnavailableException("No providers registered. Please try again later"); } + } else { + throw new ServiceUnavailableException("Couldn't get providers list. Please try again later"); } /* diff --git a/opendaylight/networkconfiguration/neutron/northbound/src/main/resources/WEB-INF/web.xml b/opendaylight/networkconfiguration/neutron/northbound/src/main/resources/WEB-INF/web.xml index dccd1332c9..08a314a42e 100644 --- a/opendaylight/networkconfiguration/neutron/northbound/src/main/resources/WEB-INF/web.xml +++ b/opendaylight/networkconfiguration/neutron/northbound/src/main/resources/WEB-INF/web.xml @@ -9,6 +9,11 @@ javax.ws.rs.Application org.opendaylight.controller.networkconfig.neutron.northbound.NeutronNorthboundRSApplication + + + com.sun.jersey.spi.container.ContainerRequestFilters + org.opendaylight.aaa.sts.TokenAuthFilter + 1 @@ -16,34 +21,37 @@ JAXRSNeutron /* - - - NB api - /* - - - System-Admin - Network-Admin - Network-Operator - Container-User - - - - - System-Admin - - - Network-Admin - - - Network-Operator - - - Container-User - - - BASIC - opendaylight - + + cross-origin-restconf + org.eclipse.jetty.servlets.CrossOriginFilter + + allowedOrigins + * + + + allowedMethods + GET,POST,OPTIONS,DELETE,PUT,HEAD + + + allowedHeaders + origin, content-type, accept, authorization + + + + cross-origin-restconf + /* + + + + NB api + /* + POST + GET + PUT + PATCH + DELETE + HEAD + + diff --git a/opendaylight/networkconfiguration/neutron/pom.xml b/opendaylight/networkconfiguration/neutron/pom.xml index a9ead67860..ee206d4dbd 100644 --- a/opendaylight/networkconfiguration/neutron/pom.xml +++ b/opendaylight/networkconfiguration/neutron/pom.xml @@ -10,9 +10,6 @@ networkconfig.neutron 0.5.0-SNAPSHOT bundle - - 1.26.2 - commons-net diff --git a/opendaylight/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/NeutronPort.java b/opendaylight/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/NeutronPort.java index a529599a4c..3853988353 100644 --- a/opendaylight/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/NeutronPort.java +++ b/opendaylight/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/NeutronPort.java @@ -8,6 +8,7 @@ package org.opendaylight.controller.networkconfig.neutron; + import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; @@ -62,6 +63,16 @@ public class NeutronPort implements Serializable, INeutronObject { @XmlElement (name="security_groups") List securityGroups; + @XmlElement (namespace= "binding", name="host_id") + String bindinghostID; + + @XmlElement (namespace= "binding", name="vnic_type") + String bindingvnicType; + + @XmlElement (namespace= "binding", name="vif_type") + String bindingvifType; + + /* this attribute stores the floating IP address assigned to * each fixed IP address */ @@ -169,6 +180,30 @@ public class NeutronPort implements Serializable, INeutronObject { this.securityGroups = securityGroups; } + public String getBindinghostID() { + return bindinghostID; + } + + public void setBindinghostID(String bindinghostID) { + this.bindinghostID = bindinghostID; + } + + public String getBindingvnicType() { + return bindingvnicType; + } + + public void setBindingvnicType(String bindingvnicType) { + this.bindingvnicType = bindingvnicType; + } + + public String getBindingvifType() { + return bindingvifType; + } + + public void setBindingvifType(String bindingvifType) { + this.bindingvifType = bindingvifType; + } + public NeutronFloatingIP getFloatingIP(String key) { if (!floatingIPMap.containsKey(key)) { return null; @@ -271,6 +306,8 @@ public class NeutronPort implements Serializable, INeutronObject { return "NeutronPort [portUUID=" + portUUID + ", networkUUID=" + networkUUID + ", name=" + name + ", adminStateUp=" + adminStateUp + ", status=" + status + ", macAddress=" + macAddress + ", fixedIPs=" + fixedIPs + ", deviceID=" + deviceID + ", deviceOwner=" + deviceOwner + ", tenantID=" - + tenantID + ", floatingIPMap=" + floatingIPMap + ", securityGroups=" + securityGroups + "]"; + + tenantID + ", floatingIPMap=" + floatingIPMap + ", securityGroups=" + securityGroups + + ", bindinghostID=" + bindinghostID + ", bindingvnicType=" + bindingvnicType + + ", bindingvnicType=" + bindingvnicType + "]"; } } diff --git a/pom.xml b/pom.xml index 1217d72066..d41b51b214 100644 --- a/pom.xml +++ b/pom.xml @@ -11,11 +11,8 @@ releasepom 0.2.0-SNAPSHOT pom - controller - - - 3.0 - + controller + @@ -33,6 +30,7 @@ opendaylight/networkconfiguration/neutron opendaylight/networkconfiguration/neutron/implementation opendaylight/networkconfiguration/neutron/northbound + opendaylight/networkconfiguration/neutron/features opendaylight/commons/concepts @@ -43,6 +41,7 @@ opendaylight/commons/logback_settings opendaylight/commons/filter-valve opendaylight/commons/liblldp + opendaylight/commons/enunciate-parent karaf