From: Moiz Raja Date: Tue, 10 Feb 2015 22:11:40 +0000 (+0000) Subject: Merge "Bug 2669: Use slf4j Logger instead of akka LoggingAdapter" X-Git-Tag: release/lithium~595 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=726ee824671781b5031c0108794c22bd0d96eaad;hp=e4c11407593914ed4520253909d0d7669e51cfac Merge "Bug 2669: Use slf4j Logger instead of akka LoggingAdapter" --- 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/src/main/resources/features.xml b/features/netconf/src/main/resources/features.xml index 9de15630c3..2affa27d19 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,13 @@ 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 + 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 5310db30a7..6a9b4bef2a 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/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/raft/behaviors/AbstractLeader.java b/opendaylight/md-sal/sal-akka-raft/src/main/java/org/opendaylight/controller/cluster/raft/behaviors/AbstractLeader.java index 0927b0a531..31464c5aff 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 @@ -556,7 +556,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) @@ -589,6 +589,7 @@ public abstract class AbstractLeader extends AbstractRaftActorBehavior { actor().tell(new CaptureSnapshot(lastIndex(), lastTerm(), lastAppliedIndex, lastAppliedTerm, isInstallSnapshotInitiated), actor()); + context.setSnapshotCaptureInitiated(true); } @@ -625,8 +626,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() 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..cf7af439e5 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 @@ -46,6 +46,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; @@ -1119,6 +1120,88 @@ 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(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 95ec0a6f2f..666cea69ec 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 @@ -1,8 +1,5 @@ package org.opendaylight.controller.cluster.raft.behaviors; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import akka.actor.ActorRef; import akka.actor.PoisonPill; import akka.actor.Props; @@ -46,6 +43,10 @@ import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; import org.opendaylight.controller.protobuff.messages.cluster.raft.InstallSnapshotMessages; import scala.concurrent.duration.FiniteDuration; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + public class LeaderTest extends AbstractRaftActorBehaviorTest { private final ActorRef leaderActor = @@ -444,6 +445,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()); + }}; } 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..a64e3600f5 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(), // @@ -123,7 +123,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-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-netconf-connector/pom.xml b/opendaylight/md-sal/sal-netconf-connector/pom.xml index c8836d1b88..add889fa3e 100644 --- a/opendaylight/md-sal/sal-netconf-connector/pom.xml +++ b/opendaylight/md-sal/sal-netconf-connector/pom.xml @@ -61,6 +61,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..460e072d9a 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 @@ -26,7 +26,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 +50,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; @@ -97,14 +97,14 @@ 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 = @@ -124,7 +124,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co return new MyAutoCloseable(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," + @@ -170,11 +170,11 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co } private static final class MyAutoCloseable implements AutoCloseable { - private final RemoteDeviceHandler salFacade; + private final RemoteDeviceHandler salFacade; private final NetconfDeviceCommunicator listener; public MyAutoCloseable(final NetconfDeviceCommunicator listener, - final RemoteDeviceHandler salFacade) { + final RemoteDeviceHandler salFacade) { this.listener = listener; this.salFacade = salFacade; } 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..9423dbf1d2 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 @@ -16,5 +16,7 @@ public interface RemoteDevice { 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..39340fa166 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,10 +31,12 @@ 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.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.util.RemoteDeviceId; +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; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException; @@ -51,7 +54,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); @@ -65,7 +68,7 @@ public final class NetconfDevice implements RemoteDevice salFacade; + private final RemoteDeviceHandler salFacade; private final ListeningExecutorService processingExecutor; private final SchemaSourceRegistry schemaRegistry; private final MessageTransformer messageTransformer; @@ -73,7 +76,7 @@ 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.id = id; this.schemaRegistry = schemaResourcesDTO.getSchemaRegistry(); @@ -86,7 +89,7 @@ public final class NetconfDevice implements RemoteDevice listener) { // SchemaContext setup has to be performed in a dedicated thread since // we are in a netty thread in this method @@ -119,9 +122,10 @@ 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 +296,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 +317,7 @@ public final class NetconfDevice implements RemoteDevice requiredSources) { logger.trace("{}: Trying to build schema context from {}", id, requiredSources); @@ -322,6 +334,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 +346,15 @@ public final class NetconfDevice implements RemoteDevice unresolvedSources = resolutionException.getUnsatisfiedImports().keySet(); + capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(unresolvedSources), 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 +373,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/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..556fc2f1d2 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,16 +81,16 @@ 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(); @@ -103,6 +104,17 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, } else { initFuture = dispatch.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()); + } + } + }); } private void tearDown( String reason ) { 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 88% 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..572885bcef 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); } @@ -110,17 +110,17 @@ public final class NetconfSessionCapabilities { || 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 +132,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 +176,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 +184,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/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/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..0ddafa375f 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; @@ -39,7 +40,7 @@ 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.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,7 +100,7 @@ public class NetconfDeviceTest { public void testNetconfDeviceFailFirstSchemaFailSecondEmpty() throws Exception { final ArrayList capList = Lists.newArrayList(TEST_CAPABILITY); - final RemoteDeviceHandler facade = getFacade(); + final RemoteDeviceHandler facade = getFacade(); final RemoteDeviceCommunicator listener = getListener(); final SchemaContextFactory schemaFactory = getSchemaFactory(); @@ -116,7 +117,7 @@ public class NetconfDeviceTest { = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), schemaFactory, stateSchemasResolver); final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer()); // 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,7 +127,7 @@ public class NetconfDeviceTest { @Test public void testNetconfDeviceMissingSource() throws Exception { - final RemoteDeviceHandler facade = getFacade(); + final RemoteDeviceHandler facade = getFacade(); final RemoteDeviceCommunicator listener = getListener(); final SchemaContextFactory schemaFactory = getSchemaFactory(); @@ -148,10 +149,10 @@ public class NetconfDeviceTest { = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), schemaFactory, stateSchemasResolver); final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer()); // 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,7 +166,7 @@ public class NetconfDeviceTest { @Test public void testNotificationBeforeSchema() throws Exception { - final RemoteDeviceHandler facade = getFacade(); + final RemoteDeviceHandler facade = getFacade(); final RemoteDeviceCommunicator listener = getListener(); final MessageTransformer messageTransformer = getMessageTransformer(); @@ -179,7 +180,7 @@ public class NetconfDeviceTest { 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,7 +195,7 @@ public class NetconfDeviceTest { @Test public void testNetconfDeviceReconnect() throws Exception { - final RemoteDeviceHandler facade = getFacade(); + final RemoteDeviceHandler facade = getFacade(); final RemoteDeviceCommunicator listener = getListener(); final SchemaContextFactory schemaContextProviderFactory = getSchemaFactory(); @@ -203,13 +204,13 @@ public class NetconfDeviceTest { 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 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,7 +295,7 @@ public class NetconfDeviceTest { capabilities.addAll(additionalCapabilities); - return NetconfSessionCapabilities.fromStrings( + return NetconfSessionPreferences.fromStrings( capabilities); } 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..fad3d8e1ea 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 @@ -77,7 +77,7 @@ public class NetconfDeviceCommunicatorTest { NetconfClientSession mockSession; @Mock - RemoteDevice mockDevice; + RemoteDevice mockDevice; NetconfDeviceCommunicator communicator; @@ -92,7 +92,7 @@ public class NetconfDeviceCommunicatorTest { void setupSession() { doReturn( Collections.emptySet() ).when( mockSession ).getServerCapabilities(); - doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionCapabilities.class ), + doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionPreferences.class ), any( RemoteDeviceCommunicator.class ) ); communicator.onSessionUp( mockSession ); } @@ -130,8 +130,8 @@ public class NetconfDeviceCommunicatorTest { testCapability ); doReturn( serverCapabilities ).when( mockSession ).getServerCapabilities(); - ArgumentCaptor netconfSessionCapabilities = - ArgumentCaptor.forClass( NetconfSessionCapabilities.class ); + ArgumentCaptor netconfSessionCapabilities = + ArgumentCaptor.forClass( NetconfSessionPreferences.class ); doNothing().when( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) ); communicator.onSessionUp( mockSession ); @@ -139,7 +139,7 @@ public class NetconfDeviceCommunicatorTest { verify( mockSession ).getServerCapabilities(); verify( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) ); - NetconfSessionCapabilities actualCapabilites = netconfSessionCapabilities.getValue(); + NetconfSessionPreferences actualCapabilites = netconfSessionCapabilities.getValue(); assertEquals( "containsModuleCapability", true, actualCapabilites.containsNonModuleCapability( NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()) ); assertEquals( "containsModuleCapability", false, actualCapabilites.containsNonModuleCapability(testCapability) ); @@ -340,7 +340,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() { 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/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-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..a938fbf565 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 @@ -60,7 +60,7 @@ 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 +199,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(RemoteDeviceCommunicator.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/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 +