<feature version='${mdsal.version}'>odl-mdsal-common</feature>
<feature version='${config.version}'>odl-config-startup</feature>
<feature version='${config.version}'>odl-config-netty</feature>
+ <bundle>mvn:com.lmax/disruptor/${lmax.version}</bundle>
<bundle>mvn:org.opendaylight.controller/sal-core-api/${project.version}</bundle>
<bundle>mvn:org.opendaylight.controller/sal-core-spi/${project.version}</bundle>
<bundle>mvn:org.opendaylight.controller/sal-broker-impl/${project.version}</bundle>
<bundle>mvn:org.opendaylight.controller/netconf-api/${project.version}</bundle>
<bundle>mvn:org.opendaylight.controller/netconf-auth/${project.version}</bundle>
<bundle>mvn:org.opendaylight.controller/ietf-netconf-monitoring/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/ietf-netconf/${project.version}</bundle>
+ <bundle>mvn:org.opendaylight.controller/ietf-netconf-notifications/${project.version}</bundle>
<bundle>mvn:org.opendaylight.controller/ietf-netconf-monitoring-extension/${project.version}</bundle>
<bundle>mvn:org.opendaylight.yangtools.model/ietf-inet-types/${ietf-inet-types.version}</bundle>
<bundle>mvn:org.opendaylight.yangtools.model/ietf-yang-types/${ietf-yang-types.version}</bundle>
<feature version='${project.version}'>odl-config-netconf-connector</feature>
<!-- Netconf will not provide schemas without monitoring -->
<feature version='${project.version}'>odl-netconf-monitoring</feature>
+ <feature version='${project.version}'>odl-netconf-notifications-impl</feature>
<bundle>mvn:org.opendaylight.controller/netconf-impl/${project.version}</bundle>
</feature>
<feature name='odl-config-netconf-connector' version='${project.version}' description="OpenDaylight :: Netconf :: Connector">
<feature version='${project.version}'>odl-netconf-api</feature>
<feature version='${project.version}'>odl-netconf-mapping-api</feature>
<feature version='${project.version}'>odl-netconf-util</feature>
+ <feature version='${project.version}'>odl-netconf-notifications-api</feature>
<bundle>mvn:org.opendaylight.controller/config-netconf-connector/${project.version}</bundle>
</feature>
<feature name='odl-netconf-netty-util' version='${project.version}' description="OpenDaylight :: Netconf :: Netty Util">
<feature version='${project.version}'>odl-netconf-util</feature>
<bundle>mvn:org.opendaylight.controller/netconf-monitoring/${project.version}</bundle>
</feature>
+ <feature name='odl-netconf-notifications-api' version='${project.version}' description="OpenDaylight :: Netconf :: Notification :: Api">
+ <feature version='${project.version}'>odl-netconf-api</feature>
+ <bundle>mvn:org.opendaylight.controller/netconf-notifications-api/${project.version}</bundle>
+ </feature>
+ <feature name='odl-netconf-notifications-impl' version='${project.version}' description="OpenDaylight :: Netconf :: Monitoring :: Impl">
+ <feature version='${project.version}'>odl-netconf-notifications-api</feature>
+ <bundle>mvn:org.opendaylight.controller/netconf-notifications-impl/${project.version}</bundle>
+ </feature>
</features>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-karaf</artifactId>
- <version>${pax.exam.version}</version>
+ <version>${exam.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam</artifactId>
- <version>${pax.exam.version}</version>
+ <version>${exam.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<northbound.jolokia.version>1.5.0-SNAPSHOT</northbound.jolokia.version>
<opendaylight-l2-types.version>2013.08.27.7-SNAPSHOT</opendaylight-l2-types.version>
<osgi-brandfragment.web.version>0.1.0-SNAPSHOT</osgi-brandfragment.web.version>
- <pax.exam.version>4.0.0</pax.exam.version>
<parboiled.version>1.1.6</parboiled.version>
<parboiled.scala.version>1.1.6</parboiled.scala.version>
<propertymavenplugin.version>1.0-alpha-2</propertymavenplugin.version>
<yangtools.version>0.7.0-SNAPSHOT</yangtools.version>
<sshd-core.version>0.12.0</sshd-core.version>
<jmh.version>0.9.7</jmh.version>
+ <lmax.version>3.3.0</lmax.version>
</properties>
<dependencyManagement>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.lmax</groupId>
+ <artifactId>disruptor</artifactId>
+ <version>${lmax.version}</version>
+ </dependency>
+
<!-- 3rd party dependencies needed by config-->
<dependency>
<groupId>com.jcabi</groupId>
* @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<Void> createReconnectingClient(final InetSocketAddress address, final ReconnectStrategyFactory connectStrategyFactory,
final PipelineInitializer<S> initializer) {
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;
channel.pipeline().addLast(new ClosedChannelHandler(ReconnectPromise.this));
}
});
+
+ pending.addListener(new GenericFutureListener<Future<Object>>() {
+ @Override
+ public void operationComplete(Future<Object> future) throws Exception {
+ if (!future.isSuccess()) {
+ ReconnectPromise.this.setFailure(future.cause());
+ }
+ }
+ });
}
/**
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());
+ " in " + runtimeBeans);
}
+ protected RuntimeBeanEntry findFirstByNamePrefix(final Collection<RuntimeBeanEntry> 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",
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());
// 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)
actor().tell(new CaptureSnapshot(lastIndex(), lastTerm(),
lastAppliedIndex, lastAppliedTerm, isInstallSnapshotInitiated),
actor());
+ context.setSnapshotCaptureInitiated(true);
}
context.getReplicatedLog().getSnapshotIndex(),
context.getReplicatedLog().getSnapshotTerm(),
nextSnapshotChunk,
- followerToSnapshot.incrementChunkIndex(),
- followerToSnapshot.getTotalChunks(),
+ followerToSnapshot.incrementChunkIndex(),
+ followerToSnapshot.getTotalChunks(),
Optional.of(followerToSnapshot.getLastChunkHashCode())
).toSerializable(),
actor()
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;
};
}
+ @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<String, String> peerAddresses = new HashMap<>();
+ peerAddresses.put("follower-1", followerActor1.path().toString());
+ peerAddresses.put("follower-2", followerActor2.path().toString());
+
+ TestActorRef<MockRaftActor> mockActorRef = TestActorRef.create(getSystem(),
+ MockRaftActor.props(persistenceId, peerAddresses,
+ Optional.<ConfigParams>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;
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;
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 =
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<Object> captureSnapshots = MessageCollectorActor.getAllMatching(leaderActor, CaptureSnapshot.class);
+ assertEquals("CaptureSnapshot should not get invoked when initiate is in progress", 1, captureSnapshots.size());
+
}};
}
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;
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(), //
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(), //
<artifactId>guava</artifactId>
</dependency>
<dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
+ <groupId>com.lmax</groupId>
+ <artifactId>disruptor</artifactId>
</dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>ietf-yang-types</artifactId>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
--- /dev/null
+/*
+ * 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<Void> NO_LISTENERS = Futures.immediateFuture(null);
+ private static final WaitStrategy DEFAULT_STRATEGY = new SleepingWaitStrategy();
+ private static final EventHandler<DOMNotificationRouterEvent> DISPATCH_NOTIFICATIONS = new EventHandler<DOMNotificationRouterEvent>() {
+ @Override
+ public void onEvent(final DOMNotificationRouterEvent event, final long sequence, final boolean endOfBatch) throws Exception {
+ event.deliverNotification();
+
+ }
+ };
+ private static final EventHandler<DOMNotificationRouterEvent> NOTIFY_FUTURE = new EventHandler<DOMNotificationRouterEvent>() {
+ @Override
+ public void onEvent(final DOMNotificationRouterEvent event, final long sequence, final boolean endOfBatch) {
+ event.setFuture();
+ }
+ };
+
+ private final Disruptor<DOMNotificationRouterEvent> disruptor;
+ private final ExecutorService executor;
+ private volatile Multimap<SchemaPath, ListenerRegistration<? extends DOMNotificationListener>> listeners = ImmutableMultimap.of();
+
+ private DOMNotificationRouter(final ExecutorService executor, final Disruptor<DOMNotificationRouterEvent> 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<DOMNotificationRouterEvent> 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 <T extends DOMNotificationListener> ListenerRegistration<T> registerNotificationListener(final T listener, final Collection<SchemaPath> types) {
+ final ListenerRegistration<T> reg = new AbstractListenerRegistration<T>(listener) {
+ @Override
+ protected void removeRegistration() {
+ final ListenerRegistration<T> me = this;
+
+ synchronized (DOMNotificationRouter.this) {
+ listeners = ImmutableMultimap.copyOf(Multimaps.filterValues(listeners, new Predicate<ListenerRegistration<? extends DOMNotificationListener>>() {
+ @Override
+ public boolean apply(final ListenerRegistration<? extends DOMNotificationListener> input) {
+ return input != me;
+ }
+ }));
+ }
+ }
+ };
+
+ if (!types.isEmpty()) {
+ final Builder<SchemaPath, ListenerRegistration<? extends DOMNotificationListener>> b = ImmutableMultimap.builder();
+ b.putAll(listeners);
+
+ for (SchemaPath t : types) {
+ b.put(t, reg);
+ }
+
+ listeners = b.build();
+ }
+
+ return reg;
+ }
+
+ @Override
+ public <T extends DOMNotificationListener> ListenerRegistration<T> registerNotificationListener(final T listener, final SchemaPath... types) {
+ return registerNotificationListener(listener, Arrays.asList(types));
+ }
+
+ private ListenableFuture<Void> publish(final long seq, final DOMNotification notification, final Collection<ListenerRegistration<? extends DOMNotificationListener>> subscribers) {
+ final DOMNotificationRouterEvent event = disruptor.get(seq);
+ final ListenableFuture<Void> future = event.initialize(notification, subscribers);
+ disruptor.getRingBuffer().publish(seq);
+ return future;
+ }
+
+ @Override
+ public ListenableFuture<? extends Object> putNotification(final DOMNotification notification) throws InterruptedException {
+ final Collection<ListenerRegistration<? extends DOMNotificationListener>> subscribers = listeners.get(notification.getType());
+ if (subscribers.isEmpty()) {
+ return NO_LISTENERS;
+ }
+
+ final long seq = disruptor.getRingBuffer().next();
+ return publish(seq, notification, subscribers);
+ }
+
+ private ListenableFuture<? extends Object> tryPublish(final DOMNotification notification, final Collection<ListenerRegistration<? extends DOMNotificationListener>> subscribers) {
+ final long seq;
+ try {
+ seq = disruptor.getRingBuffer().tryNext();
+ } catch (InsufficientCapacityException e) {
+ return DOMNotificationPublishService.REJECTED;
+ }
+
+ return publish(seq, notification, subscribers);
+ }
+
+ @Override
+ public ListenableFuture<? extends Object> offerNotification(final DOMNotification notification) {
+ final Collection<ListenerRegistration<? extends DOMNotificationListener>> subscribers = listeners.get(notification.getType());
+ if (subscribers.isEmpty()) {
+ return NO_LISTENERS;
+ }
+
+ return tryPublish(notification, subscribers);
+ }
+
+ @Override
+ public ListenableFuture<? extends Object> offerNotification(final DOMNotification notification, final long timeout,
+ final TimeUnit unit) throws InterruptedException {
+ final Collection<ListenerRegistration<? extends DOMNotificationListener>> subscribers = listeners.get(notification.getType());
+ if (subscribers.isEmpty()) {
+ return NO_LISTENERS;
+ }
+
+ // Attempt to perform a non-blocking publish first
+ final ListenableFuture<? extends Object> 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();
+ }
+}
--- /dev/null
+/*
+ * 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<DOMNotificationRouterEvent> FACTORY = new EventFactory<DOMNotificationRouterEvent>() {
+ @Override
+ public DOMNotificationRouterEvent newInstance() {
+ return new DOMNotificationRouterEvent();
+ }
+ };
+
+ private Collection<ListenerRegistration<? extends DOMNotificationListener>> subscribers;
+ private DOMNotification notification;
+ private SettableFuture<Void> future;
+
+ private DOMNotificationRouterEvent() {
+ // Hidden on purpose, initialized in initialize()
+ }
+
+ ListenableFuture<Void> initialize(final DOMNotification notification, final Collection<ListenerRegistration<? extends DOMNotificationListener>> subscribers) {
+ this.notification = Preconditions.checkNotNull(notification);
+ this.subscribers = Preconditions.checkNotNull(subscribers);
+ this.future = SettableFuture.create();
+ return this.future;
+ }
+
+ void deliverNotification() {
+ for (ListenerRegistration<? extends DOMNotificationListener> r : subscribers) {
+ final DOMNotificationListener l = r.getInstance();
+ if (l != null) {
+ l.onNotification(notification);
+ }
+ }
+ }
+
+ void setFuture() {
+ future.set(null);
+ }
+
+}
\ No newline at end of file
*/
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
<groupId>org.opendaylight.controller.model</groupId>
<artifactId>model-inventory</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools.model</groupId>
+ <artifactId>ietf-topology</artifactId>
+ </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>sal-broker-impl</artifactId>
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;
private static final Logger logger = LoggerFactory.getLogger(NetconfConnectorModule.class);
private BundleContext bundleContext;
- private Optional<NetconfSessionCapabilities> userCapabilities;
+ private Optional<NetconfSessionPreferences> userCapabilities;
private SchemaSourceRegistry schemaRegistry;
private SchemaContextFactory schemaContextFactory;
@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<NetconfSessionCapabilities> salFacade
+ final RemoteDeviceHandler<NetconfSessionPreferences> salFacade
= new NetconfDeviceSalFacade(id, domBroker, bindingBroker, bundleContext, globalProcessingExecutor);
final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO =
return new MyAutoCloseable(listener, salFacade);
}
- private Optional<NetconfSessionCapabilities> getUserCapabilities() {
+ private Optional<NetconfSessionPreferences> getUserCapabilities() {
if(getYangModuleCapabilities() == null) {
return Optional.absent();
}
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," +
}
private static final class MyAutoCloseable implements AutoCloseable {
- private final RemoteDeviceHandler<NetconfSessionCapabilities> salFacade;
+ private final RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
private final NetconfDeviceCommunicator listener;
public MyAutoCloseable(final NetconfDeviceCommunicator listener,
- final RemoteDeviceHandler<NetconfSessionCapabilities> salFacade) {
+ final RemoteDeviceHandler<NetconfSessionPreferences> salFacade) {
this.listener = listener;
this.salFacade = salFacade;
}
void onRemoteSessionDown();
+ void onRemoteSessionFailed(Throwable throwable);
+
void onNotification(M notification);
}
void onDeviceDisconnected();
+ void onDeviceFailed(Throwable throwable);
+
void onNotification(CompositeNode domNotification);
void close();
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;
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;
/**
* This is a mediator between NetconfDeviceCommunicator and NetconfDeviceSalFacade
*/
-public final class NetconfDevice implements RemoteDevice<NetconfSessionCapabilities, NetconfMessage> {
+public final class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, NetconfMessage> {
private static final Logger logger = LoggerFactory.getLogger(NetconfDevice.class);
private final RemoteDeviceId id;
private final SchemaContextFactory schemaContextFactory;
- private final RemoteDeviceHandler<NetconfSessionCapabilities> salFacade;
+ private final RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
private final ListeningExecutorService processingExecutor;
private final SchemaSourceRegistry schemaRegistry;
private final MessageTransformer<NetconfMessage> messageTransformer;
private final NotificationHandler notificationHandler;
private final List<SchemaSourceRegistration<? extends SchemaSourceRepresentation>> sourceRegistrations = Lists.newArrayList();
- public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionCapabilities> salFacade,
+ public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
final ExecutorService globalProcessingExecutor, final MessageTransformer<NetconfMessage> messageTransformer) {
this.id = id;
this.schemaRegistry = schemaResourcesDTO.getSchemaRegistry();
}
@Override
- public void onRemoteSessionUp(final NetconfSessionCapabilities remoteSessionCapabilities,
+ public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities,
final RemoteDeviceCommunicator<NetconfMessage> listener) {
// SchemaContext setup has to be performed in a dedicated thread since
// we are in a netty thread in this method
};
Futures.addCallback(sourceResolverFuture, resolvedSourceCallback);
+
}
- private void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionCapabilities remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc) {
+ private void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc) {
updateMessageTransformer(result);
salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc);
notificationHandler.onRemoteSchemaUp();
resetMessageTransformer();
}
+ @Override
+ public void onRemoteSessionFailed(Throwable throwable) {
+ salFacade.onDeviceFailed(throwable);
+ }
+
@Override
public void onNotification(final NetconfMessage notification) {
notificationHandler.handleNotification(notification);
*/
private static class DeviceSourcesResolver implements Callable<DeviceSources> {
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;
*/
private final class RecursiveSchemaSetup implements Runnable {
private final DeviceSources deviceSources;
- private final NetconfSessionCapabilities remoteSessionCapabilities;
+ private final NetconfSessionPreferences remoteSessionCapabilities;
private final NetconfDeviceRpc deviceRpc;
private final RemoteDeviceCommunicator<NetconfMessage> listener;
+ private NetconfDeviceCapabilities capabilities;
- public RecursiveSchemaSetup(final DeviceSources deviceSources, final NetconfSessionCapabilities remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc, final RemoteDeviceCommunicator<NetconfMessage> listener) {
+ public RecursiveSchemaSetup(final DeviceSources deviceSources, final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc, final RemoteDeviceCommunicator<NetconfMessage> listener) {
this.deviceSources = deviceSources;
this.remoteSessionCapabilities = remoteSessionCapabilities;
this.deviceRpc = deviceRpc;
this.listener = listener;
+ this.capabilities = remoteSessionCapabilities.getNetconfDeviceCapabilities();
}
@Override
/**
* Recursively build schema context, in case of success or final failure notify device
*/
+ // FIXME reimplement without recursion
private void setUpSchema(final Collection<SourceIdentifier> requiredSources) {
logger.trace("{}: Trying to build schema context from {}", id, requiredSources);
@Override
public void onSuccess(final SchemaContext result) {
logger.debug("{}: Schema context built successfully from {}", id, requiredSources);
+ Collection<QName> filteredQNames = Sets.difference(remoteSessionCapabilities.getModuleBasedCaps(), capabilities.getUnresolvedCapabilites().keySet());
+ capabilities.addCapabilities(filteredQNames);
+ capabilities.addNonModuleBasedCapabilities(remoteSessionCapabilities.getNonModuleCaps());
handleSalInitializationSuccess(result, remoteSessionCapabilities, deviceRpc);
}
if (t instanceof MissingSchemaSourceException) {
final SourceIdentifier missingSource = ((MissingSchemaSourceException) t).getSourceId();
logger.warn("{}: Unable to build schema context, missing source {}, will reattempt without it", id, missingSource);
+ capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(Sets.newHashSet(missingSource)), FailureReason.MissingSource);
setUpSchema(stripMissingSource(requiredSources, missingSource));
// In case resolution error, try only with resolved sources
} else if (t instanceof SchemaResolutionException) {
// TODO check for infinite loop
final SchemaResolutionException resolutionException = (SchemaResolutionException) t;
+ final Set<SourceIdentifier> 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
Preconditions.checkState(removed, "{}: Trying to remove {} from {} failed", id, sIdToRemove, requiredSources);
return sourceIdentifiers;
}
+
+ private Collection<QName> getQNameFromSourceIdentifiers(Collection<SourceIdentifier> identifiers) {
+ Collection<QName> qNames = new HashSet<>();
+ for (SourceIdentifier source : identifiers) {
+ Optional<QName> 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<QName> 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);
+ }
}
}
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;
* 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);
}
/**
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);
}
}
/**
* 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;
--- /dev/null
+/*
+ * 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<QName, FailureReason> unresolvedCapabilites;
+ private final Set<QName> resolvedCapabilities;
+
+ private final Set<String> 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<QName> capabilities, FailureReason reason) {
+ for (QName s : capabilities) {
+ unresolvedCapabilites.put(s, reason);
+ }
+ }
+
+ public void addCapabilities(Collection<QName> availableSchemas) {
+ resolvedCapabilities.addAll(availableSchemas);
+ }
+
+ public void addNonModuleBasedCapabilities(Collection<String> nonModuleCapabilities) {
+ this.nonModuleBasedCapabilities.addAll(nonModuleCapabilities);
+ }
+
+ public Set<String> getNonModuleBasedCapabilities() {
+ return nonModuleBasedCapabilities;
+ }
+
+ public Map<QName, FailureReason> getUnresolvedCapabilites() {
+ return unresolvedCapabilites;
+ }
+
+ public Set<QName> getResolvedCapabilities() {
+ return resolvedCapabilities;
+ }
+
+}
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;
private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceCommunicator.class);
- private final RemoteDevice<NetconfSessionCapabilities, NetconfMessage> remoteDevice;
- private final Optional<NetconfSessionCapabilities> overrideNetconfCapabilities;
+ private final RemoteDevice<NetconfSessionPreferences, NetconfMessage> remoteDevice;
+ private final Optional<NetconfSessionPreferences> overrideNetconfCapabilities;
private final RemoteDeviceId id;
private final Lock sessionLock = new ReentrantLock();
private NetconfClientSession session;
private Future<?> initFuture;
- public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice<NetconfSessionCapabilities, NetconfMessage> remoteDevice,
- final NetconfSessionCapabilities netconfSessionCapabilities) {
- this(id, remoteDevice, Optional.of(netconfSessionCapabilities));
+ public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice<NetconfSessionPreferences, NetconfMessage> remoteDevice,
+ final NetconfSessionPreferences netconfSessionPreferences) {
+ this(id, remoteDevice, Optional.of(netconfSessionPreferences));
}
public NetconfDeviceCommunicator(final RemoteDeviceId id,
- final RemoteDevice<NetconfSessionCapabilities, NetconfMessage> remoteDevice) {
- this(id, remoteDevice, Optional.<NetconfSessionCapabilities>absent());
+ final RemoteDevice<NetconfSessionPreferences, NetconfMessage> remoteDevice) {
+ this(id, remoteDevice, Optional.<NetconfSessionPreferences>absent());
}
- private NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice<NetconfSessionCapabilities, NetconfMessage> remoteDevice,
- final Optional<NetconfSessionCapabilities> overrideNetconfCapabilities) {
+ private NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice<NetconfSessionPreferences, NetconfMessage> remoteDevice,
+ final Optional<NetconfSessionPreferences> overrideNetconfCapabilities) {
this.id = id;
this.remoteDevice = remoteDevice;
this.overrideNetconfCapabilities = overrideNetconfCapabilities;
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();
} else {
initFuture = dispatch.createClient(config);
}
+
+ initFuture.addListener(new GenericFutureListener<Future<Object>>(){
+
+ @Override
+ public void operationComplete(Future<Object> future) throws Exception {
+ if (!future.isSuccess()) {
+ logger.debug("{}: Connection failed", id, future.cause());
+ NetconfDeviceCommunicator.this.remoteDevice.onRemoteSessionFailed(future.cause());
+ }
+ }
+ });
}
private void tearDown( String reason ) {
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public final class NetconfSessionCapabilities {
+public final class NetconfSessionPreferences {
private static final class ParameterMatcher {
private final Predicate<String> predicate;
}
}
- 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=");
private final Set<QName> moduleBasedCaps;
private final Set<String> nonModuleCaps;
- private NetconfSessionCapabilities(final Set<String> nonModuleCaps, final Set<QName> moduleBasedCaps) {
+ private NetconfSessionPreferences(final Set<String> nonModuleCaps, final Set<QName> moduleBasedCaps) {
this.nonModuleCaps = Preconditions.checkNotNull(nonModuleCaps);
this.moduleBasedCaps = Preconditions.checkNotNull(moduleBasedCaps);
}
|| containsNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString());
}
- public NetconfSessionCapabilities replaceModuleCaps(final NetconfSessionCapabilities netconfSessionModuleCapabilities) {
+ public NetconfSessionPreferences replaceModuleCaps(final NetconfSessionPreferences netconfSessionModuleCapabilities) {
final Set<QName> 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());
}
return QName.cachedReference(QName.create(URI.create(namespace), null, moduleName).withoutRevision());
}
- public static NetconfSessionCapabilities fromStrings(final Collection<String> capabilities) {
+ public static NetconfSessionPreferences fromStrings(final Collection<String> capabilities) {
final Set<QName> moduleBasedCaps = new HashSet<>();
final Set<String> nonModuleCaps = Sets.newHashSet(capabilities);
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));
}
moduleBasedCaps.add(qName);
nonModuleCaps.remove(capability);
}
+
+ private NetconfDeviceCapabilities capabilities = new NetconfDeviceCapabilities();
+
+ public NetconfDeviceCapabilities getNetconfDeviceCapabilities() {
+ return capabilities;
+ }
+
+
}
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;
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;
*
* 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);
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDeviceHandler<NetconfSessionCapabilities> {
+public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDeviceHandler<NetconfSessionPreferences> {
private static final Logger logger= LoggerFactory.getLogger(NetconfDeviceSalFacade.class);
@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() {
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.<QName>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) {
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;
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);
final DataBroker dataBroker = session.getSALService(DataBroker.class);
datastoreAdapter = new NetconfDeviceDatastoreAdapter(id, dataBroker);
+
+ topologyDatastoreAdapter = new NetconfDeviceTopologyAdapter(id, dataBroker);
}
public void close() throws Exception {
private ObjectRegistration<DOMMountPoint> registration;
private NotificationPublishService notificationSerivce;
+ private ObjectRegistration<DOMMountPoint> 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) {
registration = mountBuilder.register();
}
+ @Deprecated
synchronized void onDeviceDisconnected() {
if(registration == null) {
return;
}
}
+ 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;
}
--- /dev/null
+/*
+ * 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<Entry<QName, FailureReason>, UnavailableCapability> UNAVAILABLE_CAPABILITY_TRANSFORMER = new Function<Entry<QName, FailureReason>, UnavailableCapability>() {
+ @Override
+ public UnavailableCapability apply(final Entry<QName, FailureReason> input) {
+ return new UnavailableCapabilityBuilder()
+ .setCapability(input.getKey().toString())
+ .setFailureReason(input.getValue()).build();
+ }
+ };
+ public static final Function<QName, String> AVAILABLE_CAPABILITY_TRANSFORMER = new Function<QName, String>() {
+ @Override
+ public String apply(QName qName) {
+ return qName.toString();
+ }
+ };
+
+ private final RemoteDeviceId id;
+ private final DataBroker dataService;
+
+ private final InstanceIdentifier<NetworkTopology> networkTopologyPath;
+ private final KeyedInstanceIdentifier<Topology, TopologyKey> 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<Node> 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<String> 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<Void, TransactionCommitFailedException> result = transaction.submit();
+
+ Futures.addCallback(result, new FutureCallback<Void>() {
+ @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();
+ }
+}
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;
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;
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;
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);
}
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;
}
};
- 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);
}
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;
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);
}
*/
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<Node> bindingPath;
private final NodeKey key;
+ private final org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier topologyPath;
+ private final InstanceIdentifier<org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node> 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<Node> createBindingPath(final NodeKey key) {
return builder.build();
}
+ private static InstanceIdentifier<org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node> createBindingPathForTopology(final NodeKey key) {
+ final InstanceIdentifier<NetworkTopology> networkTopology = InstanceIdentifier.builder(NetworkTopology.class).build();
+ final KeyedInstanceIdentifier<Topology, TopologyKey> 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;
}
return key;
}
+ public InstanceIdentifier<org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node> 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 +'}';
--- /dev/null
+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;
+ }
+}
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;
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;
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;
}
};
public void testNetconfDeviceFailFirstSchemaFailSecondEmpty() throws Exception {
final ArrayList<String> capList = Lists.newArrayList(TEST_CAPABILITY);
- final RemoteDeviceHandler<NetconfSessionCapabilities> facade = getFacade();
+ final RemoteDeviceHandler<NetconfSessionPreferences> facade = getFacade();
final RemoteDeviceCommunicator<NetconfMessage> listener = getListener();
final SchemaContextFactory schemaFactory = getSchemaFactory();
= 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();
@Test
public void testNetconfDeviceMissingSource() throws Exception {
- final RemoteDeviceHandler<NetconfSessionCapabilities> facade = getFacade();
+ final RemoteDeviceHandler<NetconfSessionPreferences> facade = getFacade();
final RemoteDeviceCommunicator<NetconfMessage> listener = getListener();
final SchemaContextFactory schemaFactory = getSchemaFactory();
= 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));
}
@Test
public void testNotificationBeforeSchema() throws Exception {
- final RemoteDeviceHandler<NetconfSessionCapabilities> facade = getFacade();
+ final RemoteDeviceHandler<NetconfSessionPreferences> facade = getFacade();
final RemoteDeviceCommunicator<NetconfMessage> listener = getListener();
final MessageTransformer<NetconfMessage> messageTransformer = getMessageTransformer();
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);
@Test
public void testNetconfDeviceReconnect() throws Exception {
- final RemoteDeviceHandler<NetconfSessionCapabilities> facade = getFacade();
+ final RemoteDeviceHandler<NetconfSessionPreferences> facade = getFacade();
final RemoteDeviceCommunicator<NetconfMessage> listener = getListener();
final SchemaContextFactory schemaContextProviderFactory = getSchemaFactory();
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();
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() {
return parser.resolveSchemaContext(models);
}
- private RemoteDeviceHandler<NetconfSessionCapabilities> getFacade() throws Exception {
- final RemoteDeviceHandler<NetconfSessionCapabilities> remoteDeviceHandler = mockCloseableClass(RemoteDeviceHandler.class);
- doNothing().when(remoteDeviceHandler).onDeviceConnected(any(SchemaContext.class), any(NetconfSessionCapabilities.class), any(RpcImplementation.class));
+ private RemoteDeviceHandler<NetconfSessionPreferences> getFacade() throws Exception {
+ final RemoteDeviceHandler<NetconfSessionPreferences> 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;
return messageTransformer;
}
- public NetconfSessionCapabilities getSessionCaps(final boolean addMonitor, final Collection<String> additionalCapabilities) {
+ public NetconfSessionPreferences getSessionCaps(final boolean addMonitor, final Collection<String> additionalCapabilities) {
final ArrayList<String> capabilities = Lists.newArrayList(
XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0,
XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1);
capabilities.addAll(additionalCapabilities);
- return NetconfSessionCapabilities.fromStrings(
+ return NetconfSessionPreferences.fromStrings(
capabilities);
}
NetconfClientSession mockSession;
@Mock
- RemoteDevice<NetconfSessionCapabilities, NetconfMessage> mockDevice;
+ RemoteDevice<NetconfSessionPreferences, NetconfMessage> mockDevice;
NetconfDeviceCommunicator communicator;
void setupSession()
{
doReturn( Collections.<String>emptySet() ).when( mockSession ).getServerCapabilities();
- doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionCapabilities.class ),
+ doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionPreferences.class ),
any( RemoteDeviceCommunicator.class ) );
communicator.onSessionUp( mockSession );
}
testCapability );
doReturn( serverCapabilities ).when( mockSession ).getServerCapabilities();
- ArgumentCaptor<NetconfSessionCapabilities> netconfSessionCapabilities =
- ArgumentCaptor.forClass( NetconfSessionCapabilities.class );
+ ArgumentCaptor<NetconfSessionPreferences> netconfSessionCapabilities =
+ ArgumentCaptor.forClass( NetconfSessionPreferences.class );
doNothing().when( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) );
communicator.onSessionUp( mockSession );
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) );
*/
@Test
public void testNetconfDeviceReconnectInCommunicator() throws Exception {
- final RemoteDevice<NetconfSessionCapabilities, NetconfMessage> device = mock(RemoteDevice.class);
+ final RemoteDevice<NetconfSessionPreferences, NetconfMessage> device = mock(RemoteDevice.class);
final TimedReconnectStrategy timedReconnectStrategy = new TimedReconnectStrategy(GlobalEventExecutor.INSTANCE, 10000, 0, 1.0, null, 100L, null);
final ReconnectStrategy reconnectStrategy = spy(new ReconnectStrategy() {
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 {
"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<String> caps2 = Lists.newArrayList(
"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));
"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());
}
--- /dev/null
+/*
+ * 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
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;
@Test
public void testDiscardChanges() {
final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc), normalizer,
- NetconfSessionCapabilities.fromStrings(Collections.<String>emptySet()));
+ NetconfSessionPreferences.fromStrings(Collections.<String>emptySet()));
final CheckedFuture<Void, TransactionCommitFailedException> submitFuture = tx.submit();
try {
submitFuture.checkedGet();
.when(rpc).invokeRpc(any(QName.class), any(CompositeNode.class));
final WriteRunningTx tx = new WriteRunningTx(id, new NetconfBaseOps(rpc), normalizer,
- NetconfSessionCapabilities.fromStrings(Collections.<String>emptySet()));
+ NetconfSessionPreferences.fromStrings(Collections.<String>emptySet()));
try {
tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
} catch (final Exception e) {
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ietf-netconf</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ietf-netconf-monitoring</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ietf-netconf-notifications</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-notifications-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-notifications-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netconf-client</artifactId>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-parser-impl</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-netconf-connector</artifactId>
+ </dependency>
</dependencies>
<build>
}
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 */
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;
* Implementation of RemoteDeviceHandler. Integrates cli with
* sal-netconf-connector.
*/
-public class NetconfDeviceConnectionHandler implements RemoteDeviceHandler<NetconfSessionCapabilities> {
+public class NetconfDeviceConnectionHandler implements RemoteDeviceHandler<NetconfSessionPreferences> {
private final CommandDispatcher commandDispatcher;
private final SchemaContextRegistry schemaContextRegistry;
@Override
public synchronized void onDeviceConnected(final SchemaContext context,
- final NetconfSessionCapabilities capabilities, final RpcImplementation rpcImplementation) {
+ final NetconfSessionPreferences preferences, final RpcImplementation rpcImplementation) {
console.enterRootContext(new ConsoleContext() {
@Override
up = false;
}
+ @Override
+ public void onDeviceFailed(Throwable throwable) {
+ // FIXME
+ }
+
@Override
public void onNotification(final CompositeNode compositeNode) {
// FIXME
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.net.InetSocketAddress;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// TODO we receive configBuilder in order to add SessionListener, Session
// Listener should not be part of config
- public synchronized void connect(final String name, final NetconfClientConfigurationBuilder configBuilder) {
+ public synchronized void connect(final String name, final InetSocketAddress address, final NetconfClientConfigurationBuilder configBuilder) {
// TODO change IllegalState exceptions to custom ConnectionException
Preconditions.checkState(listener == null, "Already connected");
- final RemoteDeviceId deviceId = new RemoteDeviceId(name);
+ final RemoteDeviceId deviceId = new RemoteDeviceId(name, address);
handler = new NetconfDeviceConnectionHandler(commandDispatcher, schemaContextRegistry,
console, name);
/**
* Blocks thread until connection is fully established
*/
- public synchronized Set<String> connectBlocking(final String name, final NetconfClientConfigurationBuilder configBuilder) {
- this.connect(name, configBuilder);
+ public synchronized Set<String> connectBlocking(final String name, final InetSocketAddress address, final NetconfClientConfigurationBuilder configBuilder) {
+ this.connect(name, address, configBuilder);
synchronized (handler) {
while (handler.isUp() == false) {
try {
@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<String> remoteCmds = connectManager.connectBlocking(addressName, config);
+ private Output invoke(final NetconfClientConfigurationBuilder config, final String addressName, final Input inputArgs) {
+ final Set<String> remoteCmds = connectManager.connectBlocking(addressName, getAdress(inputArgs), config);
final ArrayList<Node<?>> output = Lists.newArrayList();
output.add(new SimpleNodeTOImpl<>(QName.create(getCommandId(), "status"), null, "Connection initiated"));
.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 <T> Optional<T> getArgumentOpt(final Input inputArgs, final String argName, final Class<T> type) {
final QName argQName = QName.create(getCommandId(), argName);
final Node<?> argumentNode = inputArgs.getArg(argName);
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;
}
static NetconfDeviceCommunicator getSessionListener() {
- RemoteDevice<NetconfSessionCapabilities, NetconfMessage> mockedRemoteDevice = mock(RemoteDevice.class);
- doNothing().when(mockedRemoteDevice).onRemoteSessionUp(any(NetconfSessionCapabilities.class), any(RemoteDeviceCommunicator.class));
+ RemoteDevice<NetconfSessionPreferences, NetconfMessage> 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);
}
--- /dev/null
+/*
+ * 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);
+}
private static final Logger LOG = LoggerFactory.getLogger(NetconfMessageToEXIEncoder.class);
/**
* This class is not marked as shared, so it can be attached to only a single channel,
- * which means that {@link #encode(ChannelHandlerContext, NetconfMessage, ByteBuf)}
+ * which means that {@link #encode(io.netty.channel.ChannelHandlerContext, org.opendaylight.controller.netconf.api.NetconfMessage, io.netty.buffer.ByteBuf)}
* cannot be invoked concurrently. Hence we can reuse the transmogrifier.
*/
- private final Transmogrifier transmogrifier;
+ private final NetconfEXICodec codec;
- private NetconfMessageToEXIEncoder(final Transmogrifier transmogrifier) {
- this.transmogrifier = Preconditions.checkNotNull(transmogrifier);
+ private NetconfMessageToEXIEncoder(final NetconfEXICodec codec) {
+ this.codec = Preconditions.checkNotNull(codec);
}
public static NetconfMessageToEXIEncoder create(final NetconfEXICodec codec) throws EXIOptionsException, TransmogrifierException {
- return new NetconfMessageToEXIEncoder(codec.getTransmogrifier());
+ return new NetconfMessageToEXIEncoder(codec);
}
@Override
protected void encode(final ChannelHandlerContext ctx, final NetconfMessage msg, final ByteBuf out) throws EXIOptionsException, IOException, TransformerException, TransmogrifierException {
LOG.trace("Sent to encode : {}", msg);
+ // TODO Workaround for bug 2679, recreate transmogrifier every time
+ // If the transmogrifier is reused, encoded xml can become non valid according to EXI decoder
+ // Seems like a bug in the nagasena library (try newer version of the library or fix the bug inside of it)
+ // Related bugs 2459: reuse nagasena resources, 2458: upgrade nagasena to newest version
+ final Transmogrifier transmogrifier = codec.getTransmogrifier();
+
try (final OutputStream os = new ByteBufOutputStream(out)) {
transmogrifier.setOutputStream(os);
final ContentHandler handler = transmogrifier.getSAXTransmogrifier();
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>netconf-subsystem</artifactId>
+ <groupId>org.opendaylight.controller</groupId>
+ <version>0.3.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <artifactId>netconf-notifications-api</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>netconf-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>ietf-netconf-notifications</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Export-Package>org.opendaylight.controller.netconf.notifications.*</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
--- /dev/null
+/*
+ * 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
+
+}
--- /dev/null
+/*
+ * 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 {
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+
+}
--- /dev/null
+/*
+ * 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();
+
+}
--- /dev/null
+/*
+ * 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 {
+
+}
--- /dev/null
+/*
+ * 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 {
+
+}
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>netconf-subsystem</artifactId>
+ <groupId>org.opendaylight.controller</groupId>
+ <version>0.3.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <artifactId>netconf-notifications-impl</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>netconf-notifications-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>netconf-util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>binding-generator-impl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>binding-data-codec</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>xmlunit</groupId>
+ <artifactId>xmlunit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>mockito-configuration</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.controller.netconf.notifications.impl.osgi.Activator</Bundle-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
--- /dev/null
+/*
+ * 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<StreamNameType, GenericNotificationListenerReg> notificationListeners = HashMultimap.create();
+
+ @GuardedBy("this")
+ private final Set<NetconfNotificationStreamListener> streamListeners = Sets.newHashSet();
+
+ @GuardedBy("this")
+ private final Map<StreamNameType, Stream> streamMetadata = Maps.newHashMap();
+
+ @GuardedBy("this")
+ private final Multiset<StreamNameType> availableStreams = HashMultiset.create();
+
+ @GuardedBy("this")
+ private final Set<GenericNotificationPublisherReg> 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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<NotificationListenerRegistration> 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<XmlElement> filter = operationElement.getOnlyChildElementWithSameNamespaceOptionally("filter");
+ Preconditions.checkArgument(filter.isPresent() == false, "Filter element not yet supported");
+
+ // Replay not supported
+ final Optional<XmlElement> startTime = operationElement.getOnlyChildElementWithSameNamespaceOptionally("startTime");
+ Preconditions.checkArgument(startTime.isPresent() == false, "StartTime element not yet supported");
+
+ // Stop time not supported
+ final Optional<XmlElement> 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.<String>absent());
+ }
+
+ private StreamNameType parseStreamIfPresent(final XmlElement operationElement) throws NetconfDocumentedException {
+ final Optional<XmlElement> 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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> 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 {
+
+ }
+}
--- /dev/null
+/*
+ * 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<SchemaContext> 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<RpcDefinition>() {
+ @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.<Date>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<Date> 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);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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<NetconfNotificationCollector> netconfNotificationCollectorServiceRegistration;
+ private ServiceRegistration<NetconfOperationServiceFactory> operationaServiceRegistration;
+ private NetconfNotificationManager netconfNotificationManager;
+
+ @Override
+ public void start(final BundleContext context) throws Exception {
+ netconfNotificationManager = new NetconfNotificationManager();
+ netconfNotificationCollectorServiceRegistration = context.registerService(NetconfNotificationCollector.class, netconfNotificationManager, new Hashtable<String, Object>());
+
+ 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<Capability> getCapabilities() {
+ return Collections.<Capability>singleton(new NotificationsCapability());
+ }
+
+ @Override
+ public Set<NetconfOperation> getNetconfOperations() {
+ return Sets.<NetconfOperation>newHashSet(
+ new Get(netconfSessionIdForReporting, netconfNotificationManager),
+ createSubscription);
+ }
+
+ @Override
+ public void close() {
+ createSubscription.close();
+ }
+ };
+ }
+ };
+
+ operationaServiceRegistration = context.registerService(NetconfOperationServiceFactory.class, netconfOperationServiceFactory, new Hashtable<String, Object>());
+
+ }
+
+ @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<String> getModuleNamespace() {
+ return Optional.absent();
+ }
+
+ @Override
+ public Optional<String> getModuleName() {
+ return Optional.absent();
+ }
+
+ @Override
+ public Optional<String> getRevision() {
+ return Optional.absent();
+ }
+
+ @Override
+ public Optional<String> getCapabilitySchema() {
+ return Optional.absent();
+ }
+
+ @Override
+ public Collection<String> getLocation() {
+ return Collections.emptyList();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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 = "<create-subscription\n" +
+ "xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\" xmlns:netconf=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
+ "<stream>TESTSTREAM</stream>" +
+ "</create-subscription>";
+
+ @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
--- /dev/null
+/*
+ * 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),
+ "<rpc-reply message-id=\"101\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
+ "<data>\n" +
+ "<netconf xmlns=\"urn:ietf:params:xml:ns:netmod:notification\">\n" +
+ "<streams>\n" +
+ "<stream>\n" +
+ "<name>base</name>\n" +
+ "<description>description</description>\n" +
+ "<replaySupport>false</replaySupport>\n" +
+ "</stream>\n" +
+ "</streams>\n" +
+ "</netconf>\n" +
+ "</data>\n" +
+ "</rpc-reply>\n");
+
+ assertTrue(diff.toString(), diff.identical());
+ }
+
+ private Document getBlankResponse() throws IOException, SAXException {
+
+ return XmlUtil.readXmlToDocument("<rpc-reply message-id=\"101\"\n" +
+ "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
+ "<data>\n" +
+ "</data>\n" +
+ "</rpc-reply>");
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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 = "<netconf-capability-change xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-notifications\">" +
+ "<deleted-capability>uri3</deleted-capability>" +
+ "<deleted-capability>uri4</deleted-capability>" +
+ "<added-capability>uri1</added-capability>" +
+ "</netconf-capability-change>";
+
+ private static final String expectedNotification = "<notification xmlns=\"urn:ietf:params:netconf:capability:notification:1.0\">" +
+ innerNotification +
+ "<eventTime>" + new SimpleDateFormat(NetconfNotification.RFC3339_DATE_FORMAT_BLUEPRINT).format(DATE) + "</eventTime>" +
+ "</notification>";
+
+ @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
--- /dev/null
+/*
+ * 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<String> 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<String> getModuleNamespace() {
+ return Optional.of(input.getNamespace().toString());
+ }
+
+ @Override
+ public Optional<String> getModuleName() {
+ return Optional.of(input.getName());
+ }
+
+ @Override
+ public Optional<String> 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<String> getCapabilitySchema() {
+ return Optional.absent();
+ }
+
+ @Override
+ public List<String> getLocation() {
+ return Collections.emptyList();
+ }
+}
private final ScheduledExecutorService minaTimerExecutor;
private final ExecutorService nioExecutor;
+ private boolean sendFakeSchema = false;
+
public NetconfDeviceSimulator() {
// TODO make pool size configurable
this(new NioEventLoopGroup(), new HashedWheelTimer(),
final Set<Capability> capabilities = Sets.newHashSet(Collections2.transform(moduleBuilders.keySet(), new Function<ModuleBuilder, Capability>() {
@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));
+ }
}
}));
<module>netconf-ssh</module>
<module>netconf-tcp</module>
<module>netconf-monitoring</module>
+ <module>ietf-netconf</module>
<module>ietf-netconf-monitoring</module>
+ <module>ietf-netconf-notifications</module>
<module>ietf-netconf-monitoring-extension</module>
<module>netconf-connector-config</module>
<module>netconf-auth</module>
<module>netconf-usermanager</module>
<module>netconf-testtool</module>
+ <module>netconf-notifications-impl</module>
+ <module>netconf-notifications-api</module>
<module>netconf-artifacts</module>
</modules>
<artifactId>releasepom</artifactId>
<version>0.2.0-SNAPSHOT</version>
<packaging>pom</packaging>
- <name>controller</name> <!-- Used by Sonar to set project name -->
+ <name>controller</name>
+ <!-- Used by Sonar to set project name -->
<modules>