From: Tony Tkacik Date: Thu, 12 Feb 2015 17:36:39 +0000 (+0000) Subject: Merge "Do not duplicate OSGi dependencyManagement" X-Git-Tag: release/lithium~549^2~15 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=83dfe301bf2a2b1eff6883a2af3282c95d5a752e;hp=4061a49630bd90e2a4839fc5f5622e1faaebfd67 Merge "Do not duplicate OSGi dependencyManagement" --- diff --git a/features/mdsal/src/main/resources/features.xml b/features/mdsal/src/main/resources/features.xml index 1582f45789..8c166e6382 100644 --- a/features/mdsal/src/main/resources/features.xml +++ b/features/mdsal/src/main/resources/features.xml @@ -28,6 +28,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/pom.xml b/features/netconf/pom.xml index 028c16b02f..997b6a275f 100644 --- a/features/netconf/pom.xml +++ b/features/netconf/pom.xml @@ -9,7 +9,7 @@ features-netconf - pom + jar features.xml @@ -38,6 +38,18 @@ org.opendaylight.controller netconf-auth + + org.opendaylight.controller + netconf-notifications-api + + + org.opendaylight.controller + netconf-notifications-impl + + + org.opendaylight.controller + ietf-netconf + org.opendaylight.controller ietf-netconf-monitoring @@ -46,6 +58,10 @@ org.opendaylight.controller ietf-netconf-monitoring-extension + + org.opendaylight.controller + ietf-netconf-notifications + org.opendaylight.yangtools.model ietf-inet-types @@ -124,6 +140,20 @@ org.opendaylight.controller netconf-monitoring + + + org.opendaylight.yangtools + features-test + ${yangtools.version} + test + + + + org.opendaylight.controller + opendaylight-karaf-empty + ${commons.opendaylight.version} + zip + @@ -169,6 +199,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..a65502124b 100644 --- a/features/netconf/src/main/resources/features.xml +++ b/features/netconf/src/main/resources/features.xml @@ -22,6 +22,8 @@ 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} @@ -43,6 +45,7 @@ odl-config-netconf-connector odl-netconf-monitoring + odl-netconf-notifications-impl mvn:org.opendaylight.controller/netconf-impl/${project.version} @@ -50,6 +53,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 +79,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/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/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index b20afb8e79..6cc363b96f 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -144,7 +144,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 @@ -208,6 +207,7 @@ 0.7.0-SNAPSHOT 0.12.0 0.9.7 + 3.3.0 @@ -317,6 +317,12 @@ guava ${guava.version} + + com.lmax + disruptor + ${lmax.version} + + com.jcabi 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..7f5233c827 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 @@ -238,7 +238,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/ReconnectPromise.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java index aaec95a74b..865c666ad2 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,6 +15,7 @@ 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; @@ -55,6 +56,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/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/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/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/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/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..0fed63098d 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 @@ -95,4 +95,9 @@ public class FollowerLogInformationImpl implements FollowerLogInformation { stopwatch.stop(); } } + + @Override + public long timeSinceLastActivity() { + return stopwatch.elapsed(TimeUnit.MILLISECONDS); + } } 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 766b80e73d..3dc6ae469a 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; @@ -43,6 +41,8 @@ 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 +85,7 @@ import org.opendaylight.controller.protobuff.messages.cluster.raft.AppendEntries * */ public abstract class RaftActor extends AbstractUntypedPersistentActor { - protected final LoggingAdapter LOG = - Logging.getLogger(getContext().system(), this); + protected final Logger LOG = LoggerFactory.getLogger(getClass()); /** * The current state determines the current behavior of a RaftActor @@ -338,8 +337,8 @@ public abstract class RaftActor extends AbstractUntypedPersistentActor { } else if (message instanceof SaveSnapshotFailure) { SaveSnapshotFailure saveSnapshotFailure = (SaveSnapshotFailure) message; - LOG.error(saveSnapshotFailure.cause(), "{}: SaveSnapshotFailure received for snapshot Cause:", - persistenceId()); + LOG.error("{}: SaveSnapshotFailure received for snapshot Cause:", + persistenceId(), saveSnapshotFailure.cause()); context.getReplicatedLog().snapshotRollback(); 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 0e1f20b246..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 @@ -106,7 +105,7 @@ public interface RaftActorContext { * * @return */ - LoggingAdapter getLogger(); + Logger getLogger(); /** * Get a mapping of peerId's to their addresses 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 5438fe7c48..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,15 +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 { @@ -36,7 +35,7 @@ public class RaftActorContextImpl implements RaftActorContext { private final Map peerAddresses; - private final LoggingAdapter LOG; + private final Logger LOG; private final ConfigParams configParams; @@ -47,7 +46,7 @@ public class RaftActorContextImpl implements RaftActorContext { 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; @@ -115,7 +114,7 @@ public class RaftActorContextImpl implements RaftActorContext { return context.system(); } - @Override public LoggingAdapter getLogger() { + @Override public Logger getLogger() { return this.LOG; } 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 410dcee5e5..9b6c08857a 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; @@ -129,7 +128,7 @@ 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); } /** @@ -232,6 +231,8 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { purgeInMemoryLog(); } + //Send the next log entry immediately, if possible, no need to wait for heartbeat to trigger that event + sendUpdatesToFollower(followerId, followerLogInformation, false); return this; } @@ -344,6 +345,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { followerLogInformation.markFollowerActive(); if (followerToSnapshot.getChunkIndex() == reply.getChunkIndex()) { + boolean wasLastChunk = false; if (reply.isSuccess()) { if(followerToSnapshot.isLastChunk(reply.getChunkIndex())) { //this was the last chunk reply @@ -371,6 +373,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { // we can remove snapshot from the memory setSnapshot(Optional.absent()); } + wasLastChunk = true; } else { followerToSnapshot.markSendStatus(true); @@ -381,6 +384,14 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { followerToSnapshot.markSendStatus(false); } + + if (!wasLastChunk && followerToSnapshot.canSendNextChunk()) { + ActorSelection followerActor = context.getPeerActorSelection(followerId); + if(followerActor != null) { + sendSnapshotChunk(followerActor, followerId); + } + } + } else { LOG.error("{}: Chunk index {} in InstallSnapshotReply from follower {} does not match expected index {}", context.getId(), reply.getChunkIndex(), followerId, @@ -413,73 +424,83 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { context.setCommitIndex(logIndex); applyLogToStateMachine(logIndex); } else { - sendAppendEntries(); + sendAppendEntries(0); } } - private void sendAppendEntries() { + private void sendAppendEntries(long timeSinceLastActivityInterval) { // 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); + } + } + } - if (followerActor != null) { - FollowerLogInformation followerLogInformation = followerToLog.get(followerId); - long followerNextIndex = followerLogInformation.getNextIndex(); - boolean isFollowerActive = followerLogInformation.isFollowerActive(); + /** + * + * 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 + */ - FollowerToSnapshot followerToSnapshot = mapFollowerToSnapshot.get(followerId); - if (followerToSnapshot != null) { - // if install snapshot is in process , then sent next chunk if possible - if (isFollowerActive && followerToSnapshot.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(), followerId); - } + private void sendUpdatesToFollower(String followerId, FollowerLogInformation followerLogInformation, + boolean sendHeartbeat) { + + 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 (isFollowerActive && + context.getReplicatedLog().isPresent(followerNextIndex)) { + // FIXME : Sending one entry at a time + final List entries = context.getReplicatedLog().getFrom(followerNextIndex, 1); - } else { - long leaderLastIndex = context.getReplicatedLog().lastIndex(); - long leaderSnapShotIndex = context.getReplicatedLog().getSnapshotIndex(); - final List entries; - - LOG.debug("{}: Checking sendAppendEntries for {}, leaderLastIndex: {}, leaderSnapShotIndex: {}", - context.getId(), leaderLastIndex, leaderSnapShotIndex); - - if (isFollowerActive && context.getReplicatedLog().isPresent(followerNextIndex)) { - LOG.debug("{}: sendAppendEntries: {} is present for {}", context.getId(), - followerNextIndex, followerId); - - // 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(String.format("%s: InitiateInstallSnapshot to follower: %s," + - "follower-nextIndex: %s, leader-snapshot-index: %s, " + - "leader-last-index: %s", context.getId(), 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(); + sendAppendEntriesToFollower(followerActor, followerNextIndex, entries, followerId); + + } 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()); - sendAppendEntriesToFollower(followerActor, followerNextIndex, entries, followerId); + // Send heartbeat to follower whenever install snapshot is initiated. + sendAppendEntriesToFollower(followerActor, followerLogInformation.getNextIndex(), + Collections.emptyList(), followerId); + + } 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); } + } } } @@ -534,7 +555,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { // no need to capture snapshot sendSnapshotChunk(followerActor, e.getKey()); - } else { + } else if (!context.isSnapshotCaptureInitiated()) { 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) @@ -567,6 +588,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { actor().tell(new CaptureSnapshot(lastIndex(), lastTerm(), lastAppliedIndex, lastAppliedTerm, isInstallSnapshotInitiated), actor()); + context.setSnapshotCaptureInitiated(true); } @@ -603,8 +625,8 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { context.getReplicatedLog().getSnapshotIndex(), context.getReplicatedLog().getSnapshotTerm(), nextSnapshotChunk, - followerToSnapshot.incrementChunkIndex(), - followerToSnapshot.getTotalChunks(), + followerToSnapshot.incrementChunkIndex(), + followerToSnapshot.getTotalChunks(), Optional.of(followerToSnapshot.getLastChunkHashCode()) ).toSerializable(), actor() @@ -615,7 +637,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { followerToSnapshot.getTotalChunks()); } } catch (IOException e) { - LOG.error(e, "{}: InstallSnapshot failed for Leader.", context.getId()); + LOG.error("{}: InstallSnapshot failed for Leader.", context.getId(), e); } } @@ -638,7 +660,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { private void sendHeartBeat() { if (!followerToLog.isEmpty()) { - sendAppendEntries(); + sendAppendEntries(context.getConfigParams().getHeartBeatInterval().toMillis()); } } 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 99824b0bb4..075b2873e4 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,7 +10,6 @@ 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; @@ -24,6 +23,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 +46,7 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { /** * */ - protected final LoggingAdapter LOG; + protected final Logger LOG; /** * @@ -349,7 +349,7 @@ 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( + LOG.warn( "{}: Missing index {} from log. Cannot apply state. Ignoring {} to {}", context.getId(), i, i, index); break; @@ -394,7 +394,7 @@ public abstract class AbstractRaftActorBehavior implements RaftActorBehavior { try { close(); } catch (Exception e) { - LOG.error(e, "{}: Failed to close behavior : {}", context.getId(), this.state()); + LOG.error("{}: Failed to close behavior : {}", context.getId(), this.state(), e); } return behavior; 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 410b3c266c..8a0788702d 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 @@ -342,7 +342,7 @@ public class Follower extends AbstractRaftActorBehavior { snapshotTracker = null; } catch (Exception e){ - LOG.error(e, "{}: Exception in InstallSnapshot of follower", context.getId()); + LOG.error("{}: Exception in InstallSnapshot of follower", context.getId(), e); //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()); 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/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 9d3e5dcb12..4d33152b41 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 { @@ -88,7 +88,8 @@ public class MockRaftActorContext implements RaftActorContext { public void initReplicatedLog(){ this.replicatedLog = new SimpleReplicatedLog(); - this.replicatedLog.append(new MockReplicatedLogEntry(1, 1, new MockPayload(""))); + this.replicatedLog.append(new MockReplicatedLogEntry(1, 0, new MockPayload("1"))); + this.replicatedLog.append(new MockReplicatedLogEntry(1, 1, new MockPayload("2"))); } @Override public ActorRef actorOf(Props props) { @@ -144,8 +145,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() { 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 30893810f5..9e0e06c70b 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,5 +1,18 @@ package org.opendaylight.controller.cluster.raft; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.actor.PoisonPill; @@ -46,6 +59,7 @@ 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.InitiateInstallSnapshot; 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; @@ -61,20 +75,6 @@ import scala.concurrent.Future; import scala.concurrent.duration.Duration; import scala.concurrent.duration.FiniteDuration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - public class RaftActorTest extends AbstractActorTest { @@ -1119,6 +1119,91 @@ public class RaftActorTest extends AbstractActorTest { }; } + @Test + public void testFakeSnapshotsForLeaderWithInInitiateSnapshots() throws Exception { + new JavaTestKit(getSystem()) { + { + String persistenceId = "leader1"; + + ActorRef followerActor1 = + getSystem().actorOf(Props.create(MessageCollectorActor.class)); + ActorRef followerActor2 = + getSystem().actorOf(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("follower-1", followerActor1.path().toString()); + peerAddresses.put("follower-2", followerActor2.path().toString()); + + TestActorRef mockActorRef = TestActorRef.create(getSystem(), + 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); + assertEquals(5, leaderActor.getReplicatedLog().size()); + + leaderActor.onReceiveCommand(new AppendEntriesReply("follower-1", 1, true, 9, 1)); + assertEquals(5, leaderActor.getReplicatedLog().size()); + + // set the 2nd follower nextIndex to 1 which has been snapshotted + leaderActor.onReceiveCommand(new AppendEntriesReply("follower-2", 1, true, 0, 1)); + assertEquals(5, leaderActor.getReplicatedLog().size()); + + // simulate a real snapshot + leaderActor.onReceiveCommand(new InitiateInstallSnapshot()); + 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("follower-2", 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 lastApplied", 0, leaderActor.getReplicatedLog().size()); + + //reply from a slow follower after should not raise errors + leaderActor.onReceiveCommand(new AppendEntriesReply("follower-2", 1, true, 5, 1)); + assertEquals(0, leaderActor.getReplicatedLog().size()); + + mockActorRef.tell(PoisonPill.getInstance(), getRef()); + + } + }; + } + + + private ByteString fromObject(Object snapshot) throws Exception { ByteArrayOutputStream b = null; ObjectOutputStream o = null; 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..3f551b3a30 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 @@ -44,10 +44,16 @@ import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; import org.opendaylight.controller.cluster.raft.utils.DoNothingActor; import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; import org.opendaylight.controller.protobuff.messages.cluster.raft.InstallSnapshotMessages; +import org.slf4j.impl.SimpleLogger; import scala.concurrent.duration.FiniteDuration; public class LeaderTest extends AbstractRaftActorBehaviorTest { + static { + // This enables trace logging for the tests. + System.setProperty(SimpleLogger.LOG_KEY_PREFIX + MockRaftActorContext.class.getName(), "trace"); + } + private final ActorRef leaderActor = getSystem().actorOf(Props.create(DoNothingActor.class)); private final ActorRef senderActor = @@ -69,44 +75,50 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { @Test public void testThatLeaderSendsAHeartbeatMessageToAllFollowers() { new JavaTestKit(getSystem()) {{ - 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()); + String followerId = "follower"; + peerAddresses.put(followerId, followerActor.path().toString()); actorContext.setPeerAddresses(peerAddresses); + long term = 1; + actorContext.getTermInformation().update(term, ""); + Leader leader = new Leader(actorContext); - leader.handleMessage(senderActor, new SendHeartBeat()); - 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 should send an immediate heartbeat with no entries as follower is inactive. + long lastIndex = actorContext.getReplicatedLog().lastIndex(); + AppendEntries appendEntries = expectMsgClass(duration("5 seconds"), AppendEntries.class); + assertEquals("getTerm", term, appendEntries.getTerm()); + assertEquals("getPrevLogIndex", -1, appendEntries.getPrevLogIndex()); + assertEquals("getPrevLogTerm", -1, appendEntries.getPrevLogTerm()); + assertEquals("Entries size", 0, appendEntries.getEntries().size()); - assertEquals("match", out); + // The follower would normally reply - simulate that explicitly here. + leader.handleMessage(followerActor, new AppendEntriesReply( + followerId, term, true, lastIndex - 1, term)); + assertEquals("isFollowerActive", true, leader.getFollower(followerId).isFollowerActive()); + + // Sleep for the heartbeat interval so AppendEntries is sent. + Uninterruptibles.sleepUninterruptibly(actorContext.getConfigParams(). + getHeartBeatInterval().toMillis(), TimeUnit.MILLISECONDS); + + leader.handleMessage(senderActor, new SendHeartBeat()); + appendEntries = expectMsgClass(duration("5 seconds"), 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()); } }; }}; @@ -115,52 +127,51 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { @Test public void testHandleReplicateMessageSendAppendEntriesToFollower() { new JavaTestKit(getSystem()) {{ - new Within(duration("1 seconds")) { @Override protected void run() { - ActorRef followerActor = getTestActor(); - MockRaftActorContext actorContext = - (MockRaftActorContext) createActorContext(); + MockRaftActorContext actorContext = (MockRaftActorContext) createActorContext(); Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + String followerId = "follower"; + peerAddresses.put(followerId, followerActor.path().toString()); actorContext.setPeerAddresses(peerAddresses); + long term = 1; + actorContext.getTermInformation().update(term, ""); + Leader leader = new Leader(actorContext); - RaftActorBehavior raftBehavior = leader - .handleMessage(senderActor, new Replicate(null, null, - new MockRaftActorContext.MockReplicatedLogEntry(1, - 100, - new MockRaftActorContext.MockPayload("foo")) - )); + + // Leader will send an immediate heartbeat - ignore it. + expectMsgClass(duration("5 seconds"), AppendEntries.class); + + // The follower would normally reply - simulate that explicitly here. + long lastIndex = actorContext.getReplicatedLog().lastIndex(); + leader.handleMessage(followerActor, new AppendEntriesReply( + followerId, term, true, lastIndex, term)); + assertEquals("isFollowerActive", true, leader.getFollower(followerId).isFollowerActive()); + + 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(senderActor, + new Replicate(null, null, newEntry)); // 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); + AppendEntries appendEntries = expectMsgClass(duration("5 seconds"), 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()); } }; }}; @@ -169,7 +180,6 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { @Test public void testHandleReplicateMessageWhenThereAreNoFollowers() { new JavaTestKit(getSystem()) {{ - new Within(duration("1 seconds")) { @Override protected void run() { @@ -270,9 +280,12 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { leader.getFollowerToSnapshot().getNextChunk(); leader.getFollowerToSnapshot().incrementChunkIndex(); + Uninterruptibles.sleepUninterruptibly(actorContext.getConfigParams().getHeartBeatInterval().toMillis(), + TimeUnit.MILLISECONDS); + leader.handleMessage(leaderActor, new SendHeartBeat()); - AppendEntries aeproto = (AppendEntries)MessageCollectorActor.getFirstMatching( + AppendEntries aeproto = MessageCollectorActor.getFirstMatching( followerActor, AppendEntries.class); assertNotNull("AppendEntries should be sent even if InstallSnapshotReply is not " + @@ -287,9 +300,8 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { leader.handleMessage(senderActor, new SendHeartBeat()); - InstallSnapshotMessages.InstallSnapshot isproto = (InstallSnapshotMessages.InstallSnapshot) - MessageCollectorActor.getFirstMatching(followerActor, - InstallSnapshot.SERIALIZABLE_CLASS); + InstallSnapshotMessages.InstallSnapshot isproto = MessageCollectorActor.getFirstMatching(followerActor, + InstallSnapshot.SERIALIZABLE_CLASS); assertNotNull("Installsnapshot should get called for sending the next chunk of snapshot", isproto); @@ -344,6 +356,9 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { //update follower timestamp leader.markFollowerActive(followerActor.path().toString()); + Uninterruptibles.sleepUninterruptibly(actorContext.getConfigParams().getHeartBeatInterval().toMillis(), + TimeUnit.MILLISECONDS); + // this should invoke a sendinstallsnapshot as followersLastIndex < snapshotIndex RaftActorBehavior raftBehavior = leader.handleMessage( senderActor, new Replicate(null, "state-id", entry)); @@ -422,7 +437,7 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { RaftActorBehavior raftBehavior = leader.handleMessage( leaderActor, new InitiateInstallSnapshot()); - CaptureSnapshot cs = (CaptureSnapshot) MessageCollectorActor. + CaptureSnapshot cs = MessageCollectorActor. getFirstMatching(leaderActor, CaptureSnapshot.class); assertNotNull(cs); @@ -432,6 +447,12 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { assertEquals(1, cs.getLastAppliedTerm()); assertEquals(4, cs.getLastIndex()); assertEquals(2, cs.getLastTerm()); + + // if an initiate is started again when first is in progress, it shouldnt initiate Capture + raftBehavior = leader.handleMessage(leaderActor, new InitiateInstallSnapshot()); + List captureSnapshots = MessageCollectorActor.getAllMatching(leaderActor, CaptureSnapshot.class); + assertEquals("CaptureSnapshot should not get invoked when initiate is in progress", 1, captureSnapshots.size()); + }}; } @@ -472,6 +493,9 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { Leader leader = new Leader(actorContext); + // Ignore initial heartbeat. + expectMsgClass(duration("5 seconds"), AppendEntries.class); + // new entry ReplicatedLogImplEntry entry = new ReplicatedLogImplEntry(newEntryIndex, currentTerm, @@ -539,6 +563,9 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { MockLeader leader = new MockLeader(actorContext); + // Ignore initial heartbeat. + expectMsgClass(duration("5 seconds"), AppendEntries.class); + Map leadersSnapshot = new HashMap<>(); leadersSnapshot.put("1", "A"); leadersSnapshot.put("2", "B"); @@ -576,9 +603,102 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { assertEquals(snapshotIndex + 1, fli.getNextIndex()); }}; } + @Test + public void testSendSnapshotfromInstallSnapshotReply() throws Exception { + new JavaTestKit(getSystem()) {{ + + TestActorRef followerActor = + TestActorRef.create(getSystem(), Props.create(MessageCollectorActor.class), "follower-reply"); + + Map peerAddresses = new HashMap<>(); + peerAddresses.put("follower-reply", + followerActor.path().toString()); + + final int followersLastIndex = 2; + final int snapshotIndex = 3; + final int snapshotTerm = 1; + final int currentTerm = 2; + + MockRaftActorContext actorContext = + (MockRaftActorContext) createActorContext(); + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(){ + @Override + public int getSnapshotChunkSize() { + return 50; + } + }; + configParams.setHeartBeatInterval(new FiniteDuration(9, TimeUnit.SECONDS)); + configParams.setIsolatedLeaderCheckInterval(new FiniteDuration(10, TimeUnit.SECONDS)); + + actorContext.setConfigParams(configParams); + 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.handleMessage(leaderActor, new SendInstallSnapshot(bs)); + + List objectList = MessageCollectorActor.getAllMatching(followerActor, + InstallSnapshotMessages.InstallSnapshot.class); + + assertEquals(1, objectList.size()); + + Object o = objectList.get(0); + assertTrue(o instanceof InstallSnapshotMessages.InstallSnapshot); + + InstallSnapshotMessages.InstallSnapshot installSnapshot = (InstallSnapshotMessages.InstallSnapshot) o; + + assertEquals(1, installSnapshot.getChunkIndex()); + assertEquals(3, installSnapshot.getTotalChunks()); + + leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), + "follower-reply", installSnapshot.getChunkIndex(), true)); + + objectList = MessageCollectorActor.getAllMatching(followerActor, + InstallSnapshotMessages.InstallSnapshot.class); + + assertEquals(2, objectList.size()); + + installSnapshot = (InstallSnapshotMessages.InstallSnapshot) objectList.get(1); + + leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), + "follower-reply", installSnapshot.getChunkIndex(), true)); + + objectList = MessageCollectorActor.getAllMatching(followerActor, + InstallSnapshotMessages.InstallSnapshot.class); + + assertEquals(3, objectList.size()); + + installSnapshot = (InstallSnapshotMessages.InstallSnapshot) objectList.get(2); + + // Send snapshot reply one more time and make sure that a new snapshot message should not be sent to follower + leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), + "follower-reply", installSnapshot.getChunkIndex(), true)); + + objectList = MessageCollectorActor.getAllMatching(followerActor, + InstallSnapshotMessages.InstallSnapshot.class); + + // Count should still stay at 3 + assertEquals(3, objectList.size()); + }}; + } + @Test - public void testHandleInstallSnapshotReplyWithInvalidChunkIndex() throws Exception { + public void testHandleInstallSnapshotReplyWithInvalidChunkIndex() throws Exception{ new JavaTestKit(getSystem()) {{ TestActorRef followerActor = @@ -622,25 +742,29 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { leader.handleMessage(leaderActor, new SendInstallSnapshot(bs)); - Object o = MessageCollectorActor.getAllMessages(followerActor).get(0); - - assertTrue(o instanceof InstallSnapshotMessages.InstallSnapshot); + MessageCollectorActor.getAllMatching(followerActor, + InstallSnapshotMessages.InstallSnapshot.class); - InstallSnapshotMessages.InstallSnapshot installSnapshot = (InstallSnapshotMessages.InstallSnapshot) o; + InstallSnapshotMessages.InstallSnapshot installSnapshot = MessageCollectorActor.getFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); + assertNotNull(installSnapshot); assertEquals(1, installSnapshot.getChunkIndex()); assertEquals(3, installSnapshot.getTotalChunks()); + followerActor.underlyingActor().clear(); - leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), followerActor.path().toString(), -1, false)); - - leader.handleMessage(leaderActor, new SendHeartBeat()); + leader.handleMessage(followerActor, new InstallSnapshotReply(actorContext.getTermInformation().getCurrentTerm(), + followerActor.path().toString(), -1, false)); - o = MessageCollectorActor.getAllMessages(followerActor).get(1); + Uninterruptibles.sleepUninterruptibly(actorContext.getConfigParams().getHeartBeatInterval().toMillis(), + TimeUnit.MILLISECONDS); - assertTrue(o instanceof InstallSnapshotMessages.InstallSnapshot); + leader.handleMessage(leaderActor, new SendHeartBeat()); - installSnapshot = (InstallSnapshotMessages.InstallSnapshot) o; + installSnapshot = MessageCollectorActor.getFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); + assertNotNull(installSnapshot); assertEquals(1, installSnapshot.getChunkIndex()); assertEquals(3, installSnapshot.getTotalChunks()); @@ -653,9 +777,8 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { public void testHandleSnapshotSendsPreviousChunksHashCodeWhenSendingNextChunk() throws Exception { new JavaTestKit(getSystem()) { { - TestActorRef followerActor = - TestActorRef.create(getSystem(), Props.create(MessageCollectorActor.class), "follower"); + TestActorRef.create(getSystem(), Props.create(MessageCollectorActor.class), "follower-chunk"); Map peerAddresses = new HashMap<>(); peerAddresses.put(followerActor.path().toString(), @@ -695,11 +818,9 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { leader.handleMessage(leaderActor, new SendInstallSnapshot(bs)); - Object o = MessageCollectorActor.getAllMessages(followerActor).get(0); - - assertTrue(o instanceof InstallSnapshotMessages.InstallSnapshot); - - InstallSnapshotMessages.InstallSnapshot installSnapshot = (InstallSnapshotMessages.InstallSnapshot) o; + InstallSnapshotMessages.InstallSnapshot installSnapshot = MessageCollectorActor.getFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); + assertNotNull(installSnapshot); assertEquals(1, installSnapshot.getChunkIndex()); assertEquals(3, installSnapshot.getTotalChunks()); @@ -707,17 +828,13 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { int hashCode = installSnapshot.getData().hashCode(); - leader.handleMessage(followerActor, new InstallSnapshotReply(installSnapshot.getTerm(),followerActor.path().toString(),1,true )); - - leader.handleMessage(leaderActor, new SendHeartBeat()); - - Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); - - o = MessageCollectorActor.getAllMessages(followerActor).get(1); + followerActor.underlyingActor().clear(); - assertTrue(o instanceof InstallSnapshotMessages.InstallSnapshot); + leader.handleMessage(followerActor, new InstallSnapshotReply(installSnapshot.getTerm(),followerActor.path().toString(),1,true )); - installSnapshot = (InstallSnapshotMessages.InstallSnapshot) o; + installSnapshot = MessageCollectorActor.getFirstMatching( + followerActor, InstallSnapshotMessages.InstallSnapshot.class); + assertNotNull(installSnapshot); assertEquals(2, installSnapshot.getChunkIndex()); assertEquals(3, installSnapshot.getTotalChunks()); @@ -786,7 +903,12 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { @Override protected RaftActorContext createActorContext(ActorRef actorRef) { - return new MockRaftActorContext("test", getSystem(), actorRef); + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + configParams.setHeartBeatInterval(new FiniteDuration(50, TimeUnit.MILLISECONDS)); + configParams.setElectionTimeoutFactor(100000); + MockRaftActorContext context = new MockRaftActorContext("test", getSystem(), actorRef); + context.setConfigParams(configParams); + return context; } private ByteString toByteString(Map state) { @@ -815,43 +937,41 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { } public static class ForwardMessageToBehaviorActor extends MessageCollectorActor { - private static AbstractRaftActorBehavior behavior; - - public ForwardMessageToBehaviorActor(){ - - } + AbstractRaftActorBehavior behavior; @Override public void onReceive(Object message) throws Exception { + if(behavior != null) { + behavior.handleMessage(sender(), message); + } + super.onReceive(message); - behavior.handleMessage(sender(), message); } - public static void setBehavior(AbstractRaftActorBehavior behavior){ - ForwardMessageToBehaviorActor.behavior = behavior; + public static Props props() { + return Props.create(ForwardMessageToBehaviorActor.class); } } @Test public void testLeaderCreatedWithCommitIndexLessThanLastIndex() throws Exception { new JavaTestKit(getSystem()) {{ - - ActorRef leaderActor = getSystem().actorOf(Props.create(MessageCollectorActor.class)); + TestActorRef leaderActor = TestActorRef.create(getSystem(), + Props.create(ForwardMessageToBehaviorActor.class)); MockRaftActorContext leaderActorContext = - new MockRaftActorContext("leader", getSystem(), leaderActor); + new MockRaftActorContext("leader", getSystem(), leaderActor); - ActorRef followerActor = getSystem().actorOf(Props.create(ForwardMessageToBehaviorActor.class)); + TestActorRef followerActor = TestActorRef.create(getSystem(), + ForwardMessageToBehaviorActor.props()); MockRaftActorContext followerActorContext = - new MockRaftActorContext("follower", getSystem(), followerActor); + new MockRaftActorContext("follower", getSystem(), followerActor); Follower follower = new Follower(followerActorContext); - - ForwardMessageToBehaviorActor.setBehavior(follower); + followerActor.underlyingActor().behavior = follower; Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + peerAddresses.put("follower", followerActor.path().toString()); leaderActorContext.setPeerAddresses(peerAddresses); @@ -859,7 +979,7 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { //create 3 entries leaderActorContext.setReplicatedLog( - new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); + new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); leaderActorContext.setCommitIndex(1); @@ -867,34 +987,29 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { // follower too has the exact same log entries and has the same commit index followerActorContext.setReplicatedLog( - new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); + new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); followerActorContext.setCommitIndex(1); Leader leader = new Leader(leaderActorContext); - leader.markFollowerActive(followerActor.path().toString()); - - leader.handleMessage(leaderActor, new SendHeartBeat()); - - AppendEntries appendEntries = (AppendEntries) MessageCollectorActor - .getFirstMatching(followerActor, AppendEntries.class); + AppendEntries appendEntries = MessageCollectorActor.getFirstMatching(followerActor, AppendEntries.class); assertNotNull(appendEntries); assertEquals(1, appendEntries.getLeaderCommit()); - assertEquals(1, appendEntries.getEntries().get(0).getIndex()); + assertEquals(0, appendEntries.getEntries().size()); assertEquals(0, appendEntries.getPrevLogIndex()); - AppendEntriesReply appendEntriesReply = - (AppendEntriesReply) MessageCollectorActor.getFirstMatching( + AppendEntriesReply appendEntriesReply = MessageCollectorActor.getFirstMatching( leaderActor, AppendEntriesReply.class); - 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()); }}; } @@ -902,66 +1017,83 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { @Test public void testLeaderCreatedWithCommitIndexLessThanFollowersCommitIndex() throws Exception { new JavaTestKit(getSystem()) {{ - - ActorRef leaderActor = getSystem().actorOf(Props.create(MessageCollectorActor.class)); + TestActorRef leaderActor = TestActorRef.create(getSystem(), + Props.create(ForwardMessageToBehaviorActor.class)); MockRaftActorContext leaderActorContext = - new MockRaftActorContext("leader", getSystem(), leaderActor); + new MockRaftActorContext("leader", getSystem(), leaderActor); - ActorRef followerActor = getSystem().actorOf( - Props.create(ForwardMessageToBehaviorActor.class)); + TestActorRef followerActor = TestActorRef.create(getSystem(), + ForwardMessageToBehaviorActor.props()); MockRaftActorContext followerActorContext = - new MockRaftActorContext("follower", getSystem(), followerActor); + new MockRaftActorContext("follower", getSystem(), followerActor); Follower follower = new Follower(followerActorContext); - - ForwardMessageToBehaviorActor.setBehavior(follower); + followerActor.underlyingActor().behavior = follower; Map peerAddresses = new HashMap<>(); - peerAddresses.put(followerActor.path().toString(), - followerActor.path().toString()); + peerAddresses.put("follower", followerActor.path().toString()); leaderActorContext.setPeerAddresses(peerAddresses); leaderActorContext.getReplicatedLog().removeFrom(0); leaderActorContext.setReplicatedLog( - new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); + new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); leaderActorContext.setCommitIndex(1); followerActorContext.getReplicatedLog().removeFrom(0); followerActorContext.setReplicatedLog( - new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); + new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build()); // follower has the same log entries but its commit index > leaders commit index followerActorContext.setCommitIndex(2); Leader leader = new Leader(leaderActorContext); - leader.markFollowerActive(followerActor.path().toString()); - - leader.handleMessage(leaderActor, new SendHeartBeat()); - - AppendEntries appendEntries = (AppendEntries) MessageCollectorActor - .getFirstMatching(followerActor, AppendEntries.class); + // Initial heartbeat + AppendEntries appendEntries = MessageCollectorActor.getFirstMatching(followerActor, AppendEntries.class); assertNotNull(appendEntries); assertEquals(1, appendEntries.getLeaderCommit()); - assertEquals(1, appendEntries.getEntries().get(0).getIndex()); + assertEquals(0, appendEntries.getEntries().size()); assertEquals(0, appendEntries.getPrevLogIndex()); - AppendEntriesReply appendEntriesReply = - (AppendEntriesReply) MessageCollectorActor.getFirstMatching( + AppendEntriesReply appendEntriesReply = MessageCollectorActor.getFirstMatching( leaderActor, AppendEntriesReply.class); + assertNotNull(appendEntriesReply); + + assertEquals(2, appendEntriesReply.getLogLastIndex()); + assertEquals(1, appendEntriesReply.getLogLastTerm()); + + leaderActor.underlyingActor().behavior = leader; + leader.handleMessage(followerActor, appendEntriesReply); + + leaderActor.underlyingActor().clear(); + followerActor.underlyingActor().clear(); + + Uninterruptibles.sleepUninterruptibly(leaderActorContext.getConfigParams().getHeartBeatInterval().toMillis(), + TimeUnit.MILLISECONDS); + + leader.handleMessage(leaderActor, new SendHeartBeat()); + appendEntries = MessageCollectorActor.getFirstMatching(followerActor, AppendEntries.class); + assertNotNull(appendEntries); + + assertEquals(1, appendEntries.getLeaderCommit()); + assertEquals(0, appendEntries.getEntries().size()); + assertEquals(2, appendEntries.getPrevLogIndex()); + + appendEntriesReply = MessageCollectorActor.getFirstMatching(leaderActor, AppendEntriesReply.class); assertNotNull(appendEntriesReply); assertEquals(2, appendEntriesReply.getLogLastIndex()); assertEquals(1, appendEntriesReply.getLogLastTerm()); + assertEquals(1, followerActorContext.getCommitIndex()); }}; } @@ -1035,8 +1167,8 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { assertEquals(2, leaderActorContext.getCommitIndex()); ApplyLogEntries applyLogEntries = - (ApplyLogEntries) MessageCollectorActor.getFirstMatching(leaderActor, - ApplyLogEntries.class); + MessageCollectorActor.getFirstMatching(leaderActor, + ApplyLogEntries.class); assertNotNull(applyLogEntries); @@ -1170,6 +1302,98 @@ public class LeaderTest extends AbstractRaftActorBehaviorTest { }}; } + + @Test + public void testAppendEntryCallAtEndofAppendEntryReply() throws Exception { + new JavaTestKit(getSystem()) {{ + TestActorRef leaderActor = TestActorRef.create(getSystem(), + Props.create(MessageCollectorActor.class)); + + MockRaftActorContext leaderActorContext = + new MockRaftActorContext("leader", getSystem(), leaderActor); + + DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); + //configParams.setHeartBeatInterval(new FiniteDuration(9, TimeUnit.SECONDS)); + configParams.setIsolatedLeaderCheckInterval(new FiniteDuration(10, TimeUnit.SECONDS)); + + leaderActorContext.setConfigParams(configParams); + + TestActorRef followerActor = TestActorRef.create(getSystem(), + ForwardMessageToBehaviorActor.props()); + + MockRaftActorContext followerActorContext = + new MockRaftActorContext("follower-reply", getSystem(), followerActor); + + followerActorContext.setConfigParams(configParams); + + Follower follower = new Follower(followerActorContext); + followerActor.underlyingActor().behavior = follower; + + Map peerAddresses = new HashMap<>(); + peerAddresses.put("follower-reply", + followerActor.path().toString()); + + leaderActorContext.setPeerAddresses(peerAddresses); + + leaderActorContext.getReplicatedLog().removeFrom(0); + leaderActorContext.setCommitIndex(-1); + leaderActorContext.setLastApplied(-1); + + followerActorContext.getReplicatedLog().removeFrom(0); + followerActorContext.setCommitIndex(-1); + followerActorContext.setLastApplied(-1); + + Leader leader = new Leader(leaderActorContext); + + AppendEntriesReply appendEntriesReply = MessageCollectorActor.getFirstMatching( + leaderActor, AppendEntriesReply.class); + assertNotNull(appendEntriesReply); + System.out.println("appendEntriesReply: "+appendEntriesReply); + 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.getFirstMatching(followerActor, AppendEntries.class); + assertNotNull(appendEntries); + + // Should send first log entry + assertEquals(1, appendEntries.getLeaderCommit()); + assertEquals(0, appendEntries.getEntries().get(0).getIndex()); + assertEquals(-1, appendEntries.getPrevLogIndex()); + + appendEntriesReply = MessageCollectorActor.getFirstMatching(leaderActor, AppendEntriesReply.class); + assertNotNull(appendEntriesReply); + + assertEquals(1, appendEntriesReply.getLogLastTerm()); + assertEquals(0, appendEntriesReply.getLogLastIndex()); + + followerActor.underlyingActor().clear(); + + leader.handleAppendEntriesReply(followerActor, appendEntriesReply); + + appendEntries = MessageCollectorActor.getFirstMatching(followerActor, AppendEntries.class); + assertNotNull(appendEntries); + + // Should send second log entry + assertEquals(1, appendEntries.getLeaderCommit()); + assertEquals(1, appendEntries.getEntries().get(0).getIndex()); + }}; + } + class MockLeader extends Leader { FollowerToSnapshot fts; 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/utils/MessageCollectorActor.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/utils/MessageCollectorActor.java index 3469a956c3..c5acb1f2a4 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 @@ -13,6 +13,7 @@ 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; @@ -23,7 +24,7 @@ import scala.concurrent.duration.FiniteDuration; public class MessageCollectorActor extends UntypedActor { - private List messages = new ArrayList<>(); + private final List messages = new ArrayList<>(); @Override public void onReceive(Object message) throws Exception { if(message instanceof String){ @@ -35,6 +36,10 @@ public class MessageCollectorActor extends UntypedActor { } } + 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); @@ -53,13 +58,17 @@ public class MessageCollectorActor extends UntypedActor { * @param clazz * @return */ - public static Object getFirstMatching(ActorRef actor, Class clazz) throws Exception { - List allMessages = getAllMessages(actor); + public static T getFirstMatching(ActorRef actor, Class clazz) throws Exception { + for(int i = 0; i < 50; i++) { + List allMessages = getAllMessages(actor); - for(Object message : allMessages){ - if(message.getClass().equals(clazz)){ - return message; + for(Object message : allMessages){ + if(message.getClass().equals(clazz)){ + return (T) message; + } } + + Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); } return null; diff --git a/opendaylight/md-sal/sal-binding-it/pom.xml b/opendaylight/md-sal/sal-binding-it/pom.xml index 491e5dcb61..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 + 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 9b6d5836f0..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(), // 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 21a0cb6a88..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()) { 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..95ee21674a 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,17 +8,16 @@ 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()) { @@ -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-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 744e2c22c6..87a0fb931e 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,8 +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; @@ -220,8 +216,8 @@ public class Shard extends RaftActor { } if (message instanceof RecoveryFailure){ - LOG.error(((RecoveryFailure) message).cause(), "{}: Recovery failed because of this cause", - persistenceId()); + 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. @@ -274,7 +270,7 @@ 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", + LOG.warn("{}: Current transaction {} has timed out after {} ms - aborting", persistenceId(), cohortEntry.getTransactionID(), transactionCommitTimeout); doAbortTransaction(cohortEntry.getTransactionID(), null); @@ -322,8 +318,8 @@ public class Shard extends RaftActor { new ModificationPayload(cohortEntry.getModification())); } } catch (Exception e) { - LOG.error(e, "{} An exception occurred while preCommitting transaction {}", - persistenceId(), cohortEntry.getTransactionID()); + LOG.error("{} An exception occurred while preCommitting transaction {}", + persistenceId(), cohortEntry.getTransactionID(), e); shardMBean.incrementFailedTransactionsCount(); getSender().tell(new akka.actor.Status.Failure(e), getSelf()); } @@ -376,7 +372,8 @@ public class Shard extends RaftActor { } catch (Exception e) { sender.tell(new akka.actor.Status.Failure(e), getSelf()); - LOG.error(e, "{}, An exception occurred while committing transaction {}", persistenceId(), transactionID); + LOG.error("{}, An exception occurred while committing transaction {}", persistenceId(), + transactionID, e); shardMBean.incrementFailedTransactionsCount(); } finally { commitCoordinator.currentTransactionComplete(transactionID, true); @@ -445,7 +442,7 @@ public class Shard extends RaftActor { @Override public void onFailure(final Throwable t) { - LOG.error(t, "{}: An exception happened during abort", persistenceId()); + LOG.error("{}: An exception happened during abort", persistenceId(), t); if(sender != null) { sender.tell(new akka.actor.Status.Failure(t), self); @@ -580,7 +577,7 @@ public class Shard extends RaftActor { shardMBean.setLastCommittedTransactionTime(System.currentTimeMillis()); } catch (InterruptedException | ExecutionException e) { shardMBean.incrementFailedTransactionsCount(); - LOG.error(e, "{}: Failed to commit", persistenceId()); + LOG.error("{}: Failed to commit", persistenceId(), e); } } @@ -667,7 +664,7 @@ public class Shard extends RaftActor { try { currentLogRecoveryBatch.add(((ModificationPayload) data).getModification()); } catch (ClassNotFoundException | IOException e) { - LOG.error(e, "{}: Error extracting ModificationPayload", persistenceId()); + LOG.error("{}: Error extracting ModificationPayload", persistenceId(), e); } } else if (data instanceof CompositeModificationPayload) { currentLogRecoveryBatch.add(((CompositeModificationPayload) data).getModification()); @@ -722,7 +719,7 @@ public class Shard extends RaftActor { shardMBean.incrementCommittedTransactionCount(); } catch (InterruptedException | ExecutionException e) { shardMBean.incrementFailedTransactionsCount(); - LOG.error(e, "{}: Failed to commit", persistenceId()); + LOG.error("{}: Failed to commit", persistenceId(), e); } } } @@ -752,7 +749,7 @@ public class Shard extends RaftActor { try { applyModificationToState(clientActor, identifier, ((ModificationPayload) data).getModification()); } catch (ClassNotFoundException | IOException e) { - LOG.error(e, "{}: Error extracting ModificationPayload", persistenceId()); + LOG.error("{}: Error extracting ModificationPayload", persistenceId(), e); } } else if (data instanceof CompositeModificationPayload) { @@ -835,7 +832,7 @@ 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", persistenceId()); + LOG.error("{}: An exception occurred when applying snapshot", persistenceId(), e); } finally { LOG.info("{}: Done applying snapshot", persistenceId()); } 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 165e272d8b..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 @@ -9,7 +9,6 @@ package org.opendaylight.controller.cluster.datastore; import akka.actor.ActorRef; import akka.actor.Status; -import akka.event.LoggingAdapter; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.util.LinkedList; @@ -20,6 +19,7 @@ import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransacti import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransactionReply; import org.opendaylight.controller.cluster.datastore.modification.Modification; import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort; +import org.slf4j.Logger; /** * Coordinates commits for a shard ensuring only one concurrent 3-phase commit. @@ -36,11 +36,11 @@ public class ShardCommitCoordinator { private final int queueCapacity; - private final LoggingAdapter log; + private final Logger log; private final String name; - public ShardCommitCoordinator(long cacheExpiryTimeoutInSec, int queueCapacity, LoggingAdapter log, + public ShardCommitCoordinator(long cacheExpiryTimeoutInSec, int queueCapacity, Logger log, String name) { cohortCache = CacheBuilder.newBuilder().expireAfterAccess( cacheExpiryTimeoutInSec, TimeUnit.SECONDS).build(); 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 22e2dbd47d..3dbac003b9 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; @@ -54,6 +52,8 @@ 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; /** @@ -67,8 +67,7 @@ import scala.concurrent.duration.Duration; */ 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()); @@ -424,12 +420,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 +526,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,7 +539,7 @@ public class ShardManager extends AbstractUntypedPersistentActorWithMetering { @Override public ShardManager create() throws Exception { - return new ShardManager(type, cluster, configuration, datastoreContext); + return new ShardManager(cluster, configuration, datastoreContext); } } 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 2a97036883..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 @@ -7,7 +7,6 @@ */ package org.opendaylight.controller.cluster.datastore; -import akka.event.LoggingAdapter; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.Collection; @@ -22,6 +21,7 @@ import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.slf4j.Logger; /** * Coordinates persistence recovery of journal log entries and snapshots for a shard. Each snapshot @@ -40,10 +40,10 @@ class ShardRecoveryCoordinator { private final SchemaContext schemaContext; private final String shardName; private final ExecutorService executor; - private final LoggingAdapter log; + private final Logger log; private final String name; - ShardRecoveryCoordinator(String shardName, SchemaContext schemaContext, LoggingAdapter log, + ShardRecoveryCoordinator(String shardName, SchemaContext schemaContext, Logger log, String name) { this.schemaContext = schemaContext; this.shardName = shardName; 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..4f472266c1 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.get98thPercentile(); + + 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/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/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..3e89823718 --- /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().getIsolatedCheckInterval().length()); + 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/ThreePhaseCommitCohortProxyTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/datastore/ThreePhaseCommitCohortProxyTest.java index 75c93dd5d2..d2396e0524 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).get98thPercentile(); + 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/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-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-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/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/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-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-dummy-distributed-datastore/pom.xml b/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml index d8d1a76a70..c7ee3a5c0c 100644 --- a/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml +++ b/opendaylight/md-sal/sal-dummy-distributed-datastore/pom.xml @@ -61,6 +61,10 @@ 1.2.0-SNAPSHOT + + org.opendaylight.controller + sal-distributed-datastore + 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/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-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..b966fae3d4 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(), true); 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..9a5b239024 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 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 { 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; @@ -287,15 +349,17 @@ 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 +370,7 @@ public final class NetconfDevice implements RemoteDevice requiredSources) { logger.trace("{}: Trying to build schema context from {}", id, requiredSources); @@ -322,6 +387,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 +399,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 +426,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..b5927f0bd5 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); @@ -70,9 +72,21 @@ 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; + } + + 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/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/util/NetconfMessageTransformUtil.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java index 9eba24179f..5e3ad2c1fb 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,10 @@ public class NetconfMessageTransformUtil { public static final CompositeNode COMMIT_RPC_CONTENT = NodeFactory.createImmutableCompositeNode(NETCONF_COMMIT_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-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/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/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/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..1579d1927f 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 @@ -43,7 +43,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 +79,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; } 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..82c04a50e0 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 @@ -66,10 +66,6 @@ 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); } } 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..ef0a72c0f0 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,18 +8,12 @@ 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; @@ -28,61 +22,32 @@ 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; + private final YangStoreService yangStoreService; public NetconfOperationServiceImpl(final YangStoreService yangStoreService, final ConfigRegistryJMXClient jmxClient, - final String netconfSessionIdForReporting) throws YangStoreException { + final String netconfSessionIdForReporting) { - yangStoreSnapshot = yangStoreService.getYangStoreSnapshot(); - checkConsistencyBetweenYangStoreAndConfig(jmxClient, yangStoreSnapshot); + this.yangStoreService = yangStoreService; 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; + return setupCapabilities(yangStoreService); } @Override @@ -90,7 +55,7 @@ public class NetconfOperationServiceImpl implements NetconfOperationService { return operationProvider.getOperations(); } - private static Set setupCapabilities(final YangStoreSnapshot yangStoreSnapshot) { + private 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")); 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..de151a8969 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,224 @@ * 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.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.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: * - * @return actual view of what is available in OSGi service registry. + * 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. */ - YangStoreSnapshot getYangStoreSnapshot() throws YangStoreException; + 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"); + } + }); + + 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)); + } + + 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) { + notificationPublisher.onCapabilityChanged(computeDiff(previous, current)); + } + } + } + + private static final Function MODULE_TO_URI = new Function() { + @Override + public Uri apply(final Module input) { + final QName qName = QName.cachedReference(QName.create(input.getQNameModule(), input.getName())); + return new Uri(qName.toString()); + } + }; + + static NetconfCapabilityChange computeDiff(final YangStoreContext previous, final YangStoreContext current) { + final Sets.SetView removed = Sets.difference(previous.getModules(), current.getModules()); + final Sets.SetView added = Sets.difference(current.getModules(), previous.getModules()); + + 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 + */ + 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..f1fc27725b 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,8 +91,8 @@ 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.NetconfOperationRouter; @@ -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,7 +137,7 @@ public class NetconfMappingTest extends AbstractConfigTest { private TestImplModuleFactory factory4; @Mock - YangStoreSnapshot yangStoreSnapshot; + YangStoreContext yangStoreSnapshot; @Mock NetconfOperationRouter netconfOperationRouter; @Mock @@ -143,6 +148,13 @@ public class NetconfMappingTest extends AbstractConfigTest { @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(); @@ -151,6 +163,8 @@ public class NetconfMappingTest extends AbstractConfigTest { this.factory2 = new DepTestImplModuleFactory(); this.factory3 = new IdentityTestModuleFactory(); factory4 = new TestImplModuleFactory(); + + super.initConfigTransactionManagerImpl(new HardcodedModuleFactoriesResolver(mockedContext, this.factory, this.factory2, this.factory3, factory4)); @@ -629,13 +643,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/netconf-artifacts/pom.xml b/opendaylight/netconf/netconf-artifacts/pom.xml index eb3cac18df..3487aa7be3 100644 --- a/opendaylight/netconf/netconf-artifacts/pom.xml +++ b/opendaylight/netconf/netconf-artifacts/pom.xml @@ -108,6 +108,12 @@ ${project.version} + + ${project.groupId} + ietf-netconf + ${project.version} + + ${project.groupId} ietf-netconf-monitoring @@ -119,6 +125,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-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..2bd919df94 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 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..2178d4eedf 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 @@ -31,6 +31,7 @@ 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; @@ -147,6 +148,9 @@ 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), 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..bf1385398b 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 @@ -52,10 +52,7 @@ import org.opendaylight.controller.netconf.client.SimpleNetconfClientSessionList 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.NetconfServerSessionNegotiatorFactory; @@ -67,8 +64,10 @@ 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.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; @@ -176,7 +175,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); } @@ -246,22 +245,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 +283,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/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/src/main/java/org/opendaylight/controller/netconf/mapping/api/SessionAwareNetconfOperation.java b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/SessionAwareNetconfOperation.java new file mode 100644 index 0000000000..88c77c6666 --- /dev/null +++ b/opendaylight/netconf/netconf-mapping-api/src/main/java/org/opendaylight/controller/netconf/mapping/api/SessionAwareNetconfOperation.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.mapping.api; + +import org.opendaylight.controller.netconf.api.NetconfSession; + +public interface SessionAwareNetconfOperation extends NetconfOperation { + + void setSession(NetconfSession session); +} 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..ef950f8a78 --- /dev/null +++ b/opendaylight/netconf/netconf-notifications-impl/src/main/java/org/opendaylight/controller/netconf/notifications/impl/osgi/Activator.java @@ -0,0 +1,119 @@ +/* + * 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.Hashtable; +import java.util.Set; +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.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() { + + @Override + public NetconfOperationService createService(final String netconfSessionIdForReporting) { + return new NetconfOperationService() { + + private final CreateSubscription createSubscription = new CreateSubscription(netconfSessionIdForReporting, netconfNotificationManager); + + @Override + public Set getCapabilities() { + return Collections.singleton(new NotificationsCapability()); + } + + @Override + public Set getNetconfOperations() { + return Sets.newHashSet( + new Get(netconfSessionIdForReporting, netconfNotificationManager), + createSubscription); + } + + @Override + public void close() { + createSubscription.close(); + } + }; + } + }; + + operationaServiceRegistration = context.registerService(NetconfOperationServiceFactory.class, netconfOperationServiceFactory, new Hashtable()); + + } + + @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-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..fcb513f016 --- /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.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; + +/** + * 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/NetconfDeviceSimulator.java b/opendaylight/netconf/netconf-testtool/src/main/java/org/opendaylight/controller/netconf/test/tool/NetconfDeviceSimulator.java index 83e1f9129b..a5f4947474 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 @@ -100,6 +100,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(), @@ -119,7 +121,12 @@ public class NetconfDeviceSimulator implements Closeable { 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)); + } } })); diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index 2a5ba09673..653dd70b29 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -28,12 +28,16 @@ netconf-ssh netconf-tcp netconf-monitoring + ietf-netconf ietf-netconf-monitoring + ietf-netconf-notifications ietf-netconf-monitoring-extension netconf-connector-config netconf-auth netconf-usermanager netconf-testtool + netconf-notifications-impl + netconf-notifications-api netconf-artifacts 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/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 f588f3f17c..1c4bc4d4d5 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,8 @@ releasepom 0.2.0-SNAPSHOT pom - controller + controller +