From: Moiz Raja Date: Fri, 1 Aug 2014 23:22:09 +0000 (+0000) Subject: Merge "Snapshot changes" X-Git-Tag: release/helium~367 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=9c3a7d1aa13908ee5f0be33b63dfd2467af551be;hp=b78e7cc266d3540634ccc5b996b67b68365edbd8 Merge "Snapshot changes" --- diff --git a/features/config-netty/src/main/resources/features.xml b/features/config-netty/src/main/resources/features.xml index 3121ca01a2..f1b2d1f753 100644 --- a/features/config-netty/src/main/resources/features.xml +++ b/features/config-netty/src/main/resources/features.xml @@ -12,5 +12,6 @@ mvn:org.opendaylight.controller/threadpool-config-api/${project.version} mvn:org.opendaylight.controller/threadpool-config-impl/${project.version} odl-config-startup + mvn:org.opendaylight.controller/config-netty-config/${config.version}/xml/config \ No newline at end of file diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index be88e4a505..cddff45191 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -1240,6 +1240,26 @@ sal-rest-connector-config ${mdsal.version} + + org.opendaylight.controller + config-netty-config + ${config.version} + + + org.opendaylight.controller + md-sal-config + ${mdsal.version} + + + org.opendaylight.controller + netconf-config + ${netconf.version} + + + org.opendaylight.controller + netconf-connector-config + ${netconf.version} + org.opendaylight.controller sal-rest-docgen @@ -1496,6 +1516,11 @@ sample-toaster-provider ${mdsal.version} + + org.opendaylight.controller.samples + toaster-config + ${mdsal.version} + org.opendaylight.controller.thirdparty com.sun.jersey.jersey-servlet diff --git a/opendaylight/commons/protocol-framework/pom.xml b/opendaylight/commons/protocol-framework/pom.xml index f70698731a..774bc7c23f 100644 --- a/opendaylight/commons/protocol-framework/pom.xml +++ b/opendaylight/commons/protocol-framework/pom.xml @@ -91,6 +91,11 @@ netty-event-executor-config test + + ch.qos.logback + logback-classic + test + diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java index a62bd7da06..a05d02cd09 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/AbstractDispatcher.java @@ -7,12 +7,19 @@ */ package org.opendaylight.protocol.framework; +import java.io.Closeable; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.google.common.base.Preconditions; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; @@ -28,13 +35,6 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.concurrent.Promise; -import java.io.Closeable; -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Dispatcher class for creating servers and clients. The idea is to first create servers and clients and the run the * start method that will handle sockets in different thread. @@ -155,7 +155,7 @@ public abstract class AbstractDispatcher, L extends */ protected Future createClient(final InetSocketAddress address, final ReconnectStrategy strategy, final PipelineInitializer initializer) { final Bootstrap b = new Bootstrap(); - final ProtocolSessionPromise p = new ProtocolSessionPromise(executor, address, strategy, b); + final ProtocolSessionPromise p = new ProtocolSessionPromise<>(executor, address, strategy, b); b.option(ChannelOption.SO_KEEPALIVE, true).handler( new ChannelInitializer() { @Override @@ -165,18 +165,36 @@ public abstract class AbstractDispatcher, L extends }); customizeBootstrap(b); + setWorkerGroup(b); + setChannelFactory(b); + + p.connect(); + LOG.debug("Client created."); + return p; + } + private void setWorkerGroup(final Bootstrap b) { if (b.group() == null) { b.group(workerGroup); } + } - // There is no way to detect if this was already set by - // customizeBootstrap() - try { - b.channel(NioSocketChannel.class); - } catch (IllegalStateException e) { - LOG.trace("Not overriding channelFactory on bootstrap {}", b, e); - } + /** + * Create a client but use a pre-configured bootstrap. + * This method however replaces the ChannelInitializer in the bootstrap. All other configuration is preserved. + * + * @param address remote address + */ + protected Future createClient(final InetSocketAddress address, final ReconnectStrategy strategy, final Bootstrap bootstrap, final PipelineInitializer initializer) { + final ProtocolSessionPromise p = new ProtocolSessionPromise<>(executor, address, strategy, bootstrap); + + bootstrap.handler( + new ChannelInitializer() { + @Override + protected void initChannel(final SocketChannel ch) { + initializer.initializeChannel(ch, p); + } + }); p.connect(); LOG.debug("Client created."); @@ -195,6 +213,9 @@ public abstract class AbstractDispatcher, L extends } /** + * + * @deprecated use {@link org.opendaylight.protocol.framework.AbstractDispatcher#createReconnectingClient(java.net.InetSocketAddress, ReconnectStrategyFactory, org.opendaylight.protocol.framework.AbstractDispatcher.PipelineInitializer)} with only one reconnectStrategyFactory instead. + * * Creates a client. * * @param address remote address @@ -204,15 +225,47 @@ public abstract class AbstractDispatcher, L extends * @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 */ + @Deprecated protected Future createReconnectingClient(final InetSocketAddress address, final ReconnectStrategyFactory connectStrategyFactory, final ReconnectStrategy reestablishStrategy, final PipelineInitializer initializer) { + return createReconnectingClient(address, connectStrategyFactory, initializer); + } - final ReconnectPromise p = new ReconnectPromise(GlobalEventExecutor.INSTANCE, this, address, connectStrategyFactory, reestablishStrategy, initializer); - p.connect(); + /** + * Creates a reconnecting client. + * + * @param address remote address + * @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 + */ + protected Future createReconnectingClient(final InetSocketAddress address, final ReconnectStrategyFactory connectStrategyFactory, + final PipelineInitializer initializer) { + final Bootstrap b = new Bootstrap(); + + final ReconnectPromise p = new ReconnectPromise<>(GlobalEventExecutor.INSTANCE, this, address, connectStrategyFactory, b, initializer); + + b.option(ChannelOption.SO_KEEPALIVE, true); + customizeBootstrap(b); + setWorkerGroup(b); + setChannelFactory(b); + + p.connect(); return p; } + private void setChannelFactory(final Bootstrap b) { + // There is no way to detect if this was already set by + // customizeBootstrap() + try { + b.channel(NioSocketChannel.class); + } catch (final IllegalStateException e) { + LOG.trace("Not overriding channelFactory on bootstrap {}", b, e); + } + } + /** * @deprecated Should only be used with {@link AbstractDispatcher#AbstractDispatcher()} */ @@ -225,5 +278,4 @@ public abstract class AbstractDispatcher, L extends this.bossGroup.shutdownGracefully(); } } - } diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSessionPromise.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSessionPromise.java index a78274cca0..a38db61ead 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSessionPromise.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ProtocolSessionPromise.java @@ -7,6 +7,7 @@ */ package org.opendaylight.protocol.framework; +import com.google.common.base.Preconditions; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; @@ -16,17 +17,12 @@ import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.Promise; - import java.net.InetSocketAddress; - import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Preconditions; - @ThreadSafe final class ProtocolSessionPromise> extends DefaultPromise { private static final Logger LOG = LoggerFactory.getLogger(ProtocolSessionPromise.class); @@ -54,72 +50,12 @@ final class ProtocolSessionPromise> extends Default LOG.debug("Promise {} attempting connect for {}ms", lock, timeout); this.b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout); - this.pending = this.b.connect(this.address).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(final ChannelFuture cf) throws Exception { - synchronized (lock) { - - LOG.debug("Promise {} connection resolved", lock); - - // Triggered when a connection attempt is resolved. - Preconditions.checkState(ProtocolSessionPromise.this.pending.equals(cf)); - - /* - * The promise we gave out could have been cancelled, - * which cascades to the connect getting cancelled, - * but there is a slight race window, where the connect - * is already resolved, but the listener has not yet - * been notified -- cancellation at that point won't - * stop the notification arriving, so we have to close - * the race here. - */ - if (isCancelled()) { - if (cf.isSuccess()) { - LOG.debug("Closing channel for cancelled promise {}", lock); - cf.channel().close(); - } - return; - } - - if (!cf.isSuccess()) { - LOG.debug("Attempt to connect to {} failed", ProtocolSessionPromise.this.address, cf.cause()); - - final Future rf = ProtocolSessionPromise.this.strategy.scheduleReconnect(cf.cause()); - rf.addListener(new FutureListener() { - @Override - public void operationComplete(final Future sf) { - synchronized (lock) { - // Triggered when a connection attempt is to be made. - Preconditions.checkState(ProtocolSessionPromise.this.pending.equals(sf)); - - /* - * The promise we gave out could have been cancelled, - * which cascades to the reconnect attempt getting - * cancelled, but there is a slight race window, where - * the reconnect attempt is already enqueued, but the - * listener has not yet been notified -- if cancellation - * happens at that point, we need to catch it here. - */ - if (!isCancelled()) { - if (sf.isSuccess()) { - connect(); - } else { - setFailure(sf.cause()); - } - } - } - } - }); - - ProtocolSessionPromise.this.pending = rf; - } else { - LOG.debug("Promise {} connection successful", lock); - } - } - } - }); + final ChannelFuture connectFuture = this.b.connect(this.address); + // Add listener that attempts reconnect by invoking this method again. + connectFuture.addListener(new BootstrapConnectListener(lock)); + this.pending = connectFuture; } catch (final Exception e) { - LOG.info("Failed to connect to {}", e); + LOG.info("Failed to connect to {}", address, e); setFailure(e); } } @@ -140,4 +76,79 @@ final class ProtocolSessionPromise> extends Default this.strategy.reconnectSuccessful(); return super.setSuccess(result); } + + private class BootstrapConnectListener implements ChannelFutureListener { + private final Object lock; + + public BootstrapConnectListener(final Object lock) { + this.lock = lock; + } + + @Override + public void operationComplete(final ChannelFuture cf) throws Exception { + synchronized (lock) { + + LOG.debug("Promise {} connection resolved", lock); + + // Triggered when a connection attempt is resolved. + Preconditions.checkState(ProtocolSessionPromise.this.pending.equals(cf)); + + /* + * The promise we gave out could have been cancelled, + * which cascades to the connect getting cancelled, + * but there is a slight race window, where the connect + * is already resolved, but the listener has not yet + * been notified -- cancellation at that point won't + * stop the notification arriving, so we have to close + * the race here. + */ + if (isCancelled()) { + if (cf.isSuccess()) { + LOG.debug("Closing channel for cancelled promise {}", lock); + cf.channel().close(); + } + return; + } + + if(cf.isSuccess()) { + LOG.debug("Promise {} connection successful", lock); + return; + } + + LOG.debug("Attempt to connect to {} failed", ProtocolSessionPromise.this.address, cf.cause()); + + final Future rf = ProtocolSessionPromise.this.strategy.scheduleReconnect(cf.cause()); + rf.addListener(new ReconnectingStrategyListener()); + ProtocolSessionPromise.this.pending = rf; + } + } + + private class ReconnectingStrategyListener implements FutureListener { + @Override + public void operationComplete(final Future sf) { + synchronized (lock) { + // Triggered when a connection attempt is to be made. + Preconditions.checkState(ProtocolSessionPromise.this.pending.equals(sf)); + + /* + * The promise we gave out could have been cancelled, + * which cascades to the reconnect attempt getting + * cancelled, but there is a slight race window, where + * the reconnect attempt is already enqueued, but the + * listener has not yet been notified -- if cancellation + * happens at that point, we need to catch it here. + */ + if (!isCancelled()) { + if (sf.isSuccess()) { + connect(); + } else { + setFailure(sf.cause()); + } + } + } + } + } + + } + } diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java index 1fa6a81753..fe1012f443 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/ReconnectPromise.java @@ -7,176 +7,100 @@ */ package org.opendaylight.protocol.framework; -import io.netty.channel.ChannelFuture; +import com.google.common.base.Preconditions; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.socket.SocketChannel; import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.Promise; - -import java.io.Closeable; import java.net.InetSocketAddress; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.opendaylight.protocol.framework.AbstractDispatcher.PipelineInitializer; - -import com.google.common.base.Preconditions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; final class ReconnectPromise, L extends SessionListener> extends DefaultPromise { + private static final Logger LOG = LoggerFactory.getLogger(ReconnectPromise.class); + private final AbstractDispatcher dispatcher; private final InetSocketAddress address; private final ReconnectStrategyFactory strategyFactory; - private final ReconnectStrategy strategy; - private final PipelineInitializer initializer; + private final Bootstrap b; + private final AbstractDispatcher.PipelineInitializer initializer; private Future pending; - private final AtomicBoolean negotiationFinished = new AtomicBoolean(false); - public ReconnectPromise(final EventExecutor executor, final AbstractDispatcher dispatcher, final InetSocketAddress address, - final ReconnectStrategyFactory connectStrategyFactory, final ReconnectStrategy reestablishStrategy, - final PipelineInitializer initializer) { + final ReconnectStrategyFactory connectStrategyFactory, final Bootstrap b, final AbstractDispatcher.PipelineInitializer initializer) { super(executor); + this.b = b; + this.initializer = Preconditions.checkNotNull(initializer); this.dispatcher = Preconditions.checkNotNull(dispatcher); this.address = Preconditions.checkNotNull(address); this.strategyFactory = Preconditions.checkNotNull(connectStrategyFactory); - this.strategy = Preconditions.checkNotNull(reestablishStrategy); - this.initializer = Preconditions.checkNotNull(initializer); } - // FIXME: BUG-190: refactor - synchronized void connect() { - negotiationFinished.set(false); - final ReconnectStrategy cs = this.strategyFactory.createReconnectStrategy(); - final ReconnectStrategy rs = new ReconnectStrategy() { - @Override - public Future scheduleReconnect(final Throwable cause) { - return cs.scheduleReconnect(cause); - } - @Override - public void reconnectSuccessful() { - cs.reconnectSuccessful(); - } - - @Override - public int getConnectTimeout() throws Exception { - final int cst = cs.getConnectTimeout(); - final int rst = ReconnectPromise.this.strategy.getConnectTimeout(); - - if (cst == 0) { - return rst; - } - if (rst == 0) { - return cst; - } - return Math.min(cst, rst); - } - }; - - final Future cf = this.dispatcher.createClient(this.address, rs, new PipelineInitializer() { + // Set up a client with pre-configured bootstrap, but add a closed channel handler into the pipeline to support reconnect attempts + pending = this.dispatcher.createClient(this.address, cs, b, new AbstractDispatcher.PipelineInitializer() { @Override public void initializeChannel(final SocketChannel channel, final Promise promise) { - addChannelClosedListener(channel.closeFuture()); initializer.initializeChannel(channel, promise); + + // add closed channel handler + channel.pipeline().addFirst(new ClosedChannelHandler(ReconnectPromise.this)); } }); + } - final Object lock = this; - this.pending = cf; + /** + * + * @return true if initial connection was established successfully, false if initial connection failed due to e.g. Connection refused, Negotiation failed + */ + private boolean isInitialConnectFinished() { + Preconditions.checkNotNull(pending); + return pending.isDone() && pending.isSuccess(); + } - cf.addListener(new FutureListener() { + @Override + public synchronized boolean cancel(final boolean mayInterruptIfRunning) { + if (super.cancel(mayInterruptIfRunning)) { + Preconditions.checkNotNull(pending); + this.pending.cancel(mayInterruptIfRunning); + return true; + } - @Override - public void operationComplete(final Future future) { - synchronized (lock) { - if (!future.isSuccess()) { - final Future rf = ReconnectPromise.this.strategy.scheduleReconnect(cf.cause()); - - if(rf == null) { - // This should reflect: no more reconnecting strategies, enough - // Currently all reconnect strategies fail with exception, should return null - return; - } - - ReconnectPromise.this.pending = rf; - - rf.addListener(new FutureListener() { - @Override - public void operationComplete(final Future sf) { - synchronized (lock) { - /* - * The promise we gave out could have been cancelled, - * which cascades to the reconnect attempt getting - * cancelled, but there is a slight race window, where - * the reconnect attempt is already enqueued, but the - * listener has not yet been notified -- if cancellation - * happens at that point, we need to catch it here. - */ - if (!isCancelled()) { - if (sf.isSuccess()) { - connect(); - } else { - setFailure(sf.cause()); - } - } - } - } - }); - } else { - /* - * FIXME: BUG-190: we have a slight race window with cancellation - * here. Analyze and define its semantics. - */ - ReconnectPromise.this.strategy.reconnectSuccessful(); - negotiationFinished.set(true); - } - } - } - }); + return false; } - private final ClosedChannelListener closedChannelListener = new ClosedChannelListener(); - - class ClosedChannelListener implements Closeable, FutureListener { + /** + * Channel handler that responds to channelInactive event and reconnects the session. + * Only if the initial connection was successfully established and promise was not canceled. + */ + private static final class ClosedChannelHandler extends ChannelInboundHandlerAdapter { + private final ReconnectPromise promise; - private final AtomicBoolean stop = new AtomicBoolean(false); + public ClosedChannelHandler(final ReconnectPromise promise) { + this.promise = promise; + } @Override - public void operationComplete(final Future future) throws Exception { - if (stop.get()) { + public void channelInactive(final ChannelHandlerContext ctx) throws Exception { + if (promise.isCancelled()) { return; } - // Start reconnecting crashed session after negotiation was successful - if (!negotiationFinished.get()) { + // Check if initial connection was fully finished. If the session was dropped during negotiation, reconnect will not happen. + // Session can be dropped during negotiation on purpose by the client side and would make no sense to initiate reconnect + if (promise.isInitialConnectFinished() == false) { return; } - connect(); - } - - @Override - public void close() { - this.stop.set(true); + LOG.debug("Reconnecting after connection to {} was dropped", promise.address); + promise.connect(); } } - private void addChannelClosedListener(final ChannelFuture channelFuture) { - channelFuture.addListener(closedChannelListener); - } - - @Override - public synchronized boolean cancel(final boolean mayInterruptIfRunning) { - closedChannelListener.close(); - - if (super.cancel(mayInterruptIfRunning)) { - this.pending.cancel(mayInterruptIfRunning); - return true; - } - - return false; - } } diff --git a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListener.java b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListener.java index 3c429fc774..a756a0da7e 100644 --- a/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListener.java +++ b/opendaylight/commons/protocol-framework/src/main/java/org/opendaylight/protocol/framework/SessionListener.java @@ -10,7 +10,7 @@ package org.opendaylight.protocol.framework; import java.util.EventListener; /** - * Listener that receives session state informations. This interface should be + * Listener that receives session state information. This interface should be * implemented by a protocol specific abstract class, that is extended by * a final class that implements the methods. */ diff --git a/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/ServerTest.java b/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/ServerTest.java index bead1ee49e..63026e384c 100644 --- a/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/ServerTest.java +++ b/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/ServerTest.java @@ -9,6 +9,14 @@ package org.opendaylight.protocol.framework; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.nio.NioEventLoopGroup; @@ -16,50 +24,139 @@ import io.netty.util.concurrent.DefaultPromise; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GlobalEventExecutor; import io.netty.util.concurrent.Promise; - +import io.netty.util.concurrent.SucceededFuture; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; public class ServerTest { SimpleDispatcher clientDispatcher, dispatcher; - final SimpleSessionListener pce = new SimpleSessionListener(); - SimpleSession session = null; ChannelFuture server = null; InetSocketAddress serverAddress; private NioEventLoopGroup eventLoopGroup; - + // Dedicated loop group for server, needed for testing reconnection client + // With dedicated server group we can simulate session drop by shutting only the server group down + private NioEventLoopGroup serverLoopGroup; @Before public void setUp() { final int port = 10000 + (int)(10000 * Math.random()); serverAddress = new InetSocketAddress("127.0.0.1", port); eventLoopGroup = new NioEventLoopGroup(); + serverLoopGroup = new NioEventLoopGroup(); + } + + @After + public void tearDown() throws IOException, InterruptedException, ExecutionException { + if(server != null) { + this.server.channel().close(); + } + this.eventLoopGroup.shutdownGracefully().get(); + this.serverLoopGroup.shutdownGracefully().get(); + try { + Thread.sleep(500); + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } } @Test - public void testConnectionEstablished() throws Exception { + public void testConnectionRefused() throws Exception { + this.clientDispatcher = getClientDispatcher(); + + final ReconnectStrategy mockReconnectStrategy = getMockedReconnectStrategy(); + + this.clientDispatcher.createClient(this.serverAddress, + mockReconnectStrategy, new SessionListenerFactory() { + @Override + public SimpleSessionListener getSessionListener() { + return new SimpleSessionListener(); + } + }); + + Mockito.verify(mockReconnectStrategy, timeout(5000).atLeast(2)).scheduleReconnect(any(Throwable.class)); + } + + @Test + public void testConnectionReestablishInitial() throws Exception { + this.clientDispatcher = getClientDispatcher(); + + final ReconnectStrategy mockReconnectStrategy = getMockedReconnectStrategy(); + + this.clientDispatcher.createClient(this.serverAddress, + mockReconnectStrategy, new SessionListenerFactory() { + @Override + public SimpleSessionListener getSessionListener() { + return new SimpleSessionListener(); + } + }); + + Mockito.verify(mockReconnectStrategy, timeout(5000).atLeast(2)).scheduleReconnect(any(Throwable.class)); + + final Promise p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); + this.dispatcher = getServerDispatcher(p); + + this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory() { + @Override + public SimpleSessionListener getSessionListener() { + return new SimpleSessionListener(); + } + }); + + this.server.get(); + + assertEquals(true, p.get(3, TimeUnit.SECONDS)); + } + + @Test + public void testConnectionDrop() throws Exception { final Promise p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); - this.dispatcher = new SimpleDispatcher(new SessionNegotiatorFactory() { + this.dispatcher = getServerDispatcher(p); + this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory() { @Override - public SessionNegotiator getSessionNegotiator(final SessionListenerFactory factory, - final Channel channel, final Promise promise) { - p.setSuccess(true); - return new SimpleSessionNegotiator(promise, channel); + public SimpleSessionListener getSessionListener() { + return new SimpleSessionListener(); } - }, new DefaultPromise(GlobalEventExecutor.INSTANCE), eventLoopGroup); + }); + + this.server.get(); + + this.clientDispatcher = getClientDispatcher(); + + final ReconnectStrategy reconnectStrategy = getMockedReconnectStrategy(); + this.session = this.clientDispatcher.createClient(this.serverAddress, + reconnectStrategy, new SessionListenerFactory() { + @Override + public SimpleSessionListener getSessionListener() { + return new SimpleSessionListener(); + } + }).get(6, TimeUnit.SECONDS); + + assertEquals(true, p.get(3, TimeUnit.SECONDS)); + + shutdownServer(); + + // No reconnect should be scheduled after server drops connection with not-reconnecting client + verify(reconnectStrategy, times(0)).scheduleReconnect(any(Throwable.class)); + } + + @Test + public void testConnectionReestablishAfterDrop() throws Exception { + final Promise p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); + + this.dispatcher = getServerDispatcher(p); this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory() { @Override @@ -70,13 +167,42 @@ public class ServerTest { this.server.get(); - this.clientDispatcher = new SimpleDispatcher(new SessionNegotiatorFactory() { + this.clientDispatcher = getClientDispatcher(); + + final ReconnectStrategyFactory reconnectStrategyFactory = mock(ReconnectStrategyFactory.class); + final ReconnectStrategy reconnectStrategy = getMockedReconnectStrategy(); + doReturn(reconnectStrategy).when(reconnectStrategyFactory).createReconnectStrategy(); + + this.clientDispatcher.createReconnectingClient(this.serverAddress, + reconnectStrategyFactory, new SessionListenerFactory() { + @Override + public SimpleSessionListener getSessionListener() { + return new SimpleSessionListener(); + } + }); + + assertEquals(true, p.get(3, TimeUnit.SECONDS)); + shutdownServer(); + + verify(reconnectStrategyFactory, timeout(20000).atLeast(2)).createReconnectStrategy(); + } + + @Test + public void testConnectionEstablished() throws Exception { + final Promise p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); + + this.dispatcher = getServerDispatcher(p); + + this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory() { @Override - public SessionNegotiator getSessionNegotiator(final SessionListenerFactory factory, - final Channel channel, final Promise promise) { - return new SimpleSessionNegotiator(promise, channel); + public SimpleSessionListener getSessionListener() { + return new SimpleSessionListener(); } - }, new DefaultPromise(GlobalEventExecutor.INSTANCE), eventLoopGroup); + }); + + this.server.get(); + + this.clientDispatcher = getClientDispatcher(); this.session = this.clientDispatcher.createClient(this.serverAddress, new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000), new SessionListenerFactory() { @@ -93,15 +219,7 @@ public class ServerTest { public void testConnectionFailed() throws IOException, InterruptedException, ExecutionException, TimeoutException { final Promise p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); - this.dispatcher = new SimpleDispatcher(new SessionNegotiatorFactory() { - - @Override - public SessionNegotiator getSessionNegotiator(final SessionListenerFactory factory, - final Channel channel, final Promise promise) { - p.setSuccess(true); - return new SimpleSessionNegotiator(promise, channel); - } - }, new DefaultPromise(GlobalEventExecutor.INSTANCE), eventLoopGroup); + this.dispatcher = getServerDispatcher(p); this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory() { @Override @@ -112,13 +230,7 @@ public class ServerTest { this.server.get(); - this.clientDispatcher = new SimpleDispatcher(new SessionNegotiatorFactory() { - @Override - public SessionNegotiator getSessionNegotiator(final SessionListenerFactory factory, - final Channel channel, final Promise promise) { - return new SimpleSessionNegotiator(promise, channel); - } - }, new DefaultPromise(GlobalEventExecutor.INSTANCE), eventLoopGroup); + this.clientDispatcher = getClientDispatcher(); this.session = this.clientDispatcher.createClient(this.serverAddress, new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000), new SessionListenerFactory() { @@ -138,14 +250,89 @@ public class ServerTest { assertFalse(session.isSuccess()); } - @After - public void tearDown() throws IOException, InterruptedException { - this.server.channel().close(); - this.eventLoopGroup.shutdownGracefully(); - try { - Thread.sleep(500); - } catch (final InterruptedException e) { - throw new RuntimeException(e); - } + @Test + public void testNegotiationFailedNoReconnect() throws Exception { + final Promise p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); + + this.dispatcher = getServerDispatcher(p); + + this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory() { + @Override + public SimpleSessionListener getSessionListener() { + return new SimpleSessionListener(); + } + }); + + this.server.get(); + + this.clientDispatcher = new SimpleDispatcher(new SessionNegotiatorFactory() { + @Override + public SessionNegotiator getSessionNegotiator(final SessionListenerFactory factory, + final Channel channel, final Promise promise) { + + return new SimpleSessionNegotiator(promise, channel) { + @Override + protected void startNegotiation() throws Exception { + negotiationFailed(new IllegalStateException("Negotiation failed")); + } + }; + } + }, new DefaultPromise(GlobalEventExecutor.INSTANCE), eventLoopGroup); + + final ReconnectStrategyFactory reconnectStrategyFactory = mock(ReconnectStrategyFactory.class); + final ReconnectStrategy reconnectStrategy = getMockedReconnectStrategy(); + doReturn(reconnectStrategy).when(reconnectStrategyFactory).createReconnectStrategy(); + + this.clientDispatcher.createReconnectingClient(this.serverAddress, + reconnectStrategyFactory, new SessionListenerFactory() { + @Override + public SimpleSessionListener getSessionListener() { + return new SimpleSessionListener(); + } + }); + + + // Only one strategy should be created for initial connect, no more = no reconnects + verify(reconnectStrategyFactory, times(1)).createReconnectStrategy(); } + + private SimpleDispatcher getClientDispatcher() { + return new SimpleDispatcher(new SessionNegotiatorFactory() { + @Override + public SessionNegotiator getSessionNegotiator(final SessionListenerFactory factory, + final Channel channel, final Promise promise) { + return new SimpleSessionNegotiator(promise, channel); + } + }, new DefaultPromise(GlobalEventExecutor.INSTANCE), eventLoopGroup); + } + + private ReconnectStrategy getMockedReconnectStrategy() throws Exception { + final ReconnectStrategy mockReconnectStrategy = mock(ReconnectStrategy.class); + final Future future = new SucceededFuture<>(GlobalEventExecutor.INSTANCE, null); + doReturn(future).when(mockReconnectStrategy).scheduleReconnect(any(Throwable.class)); + doReturn(5000).when(mockReconnectStrategy).getConnectTimeout(); + doNothing().when(mockReconnectStrategy).reconnectSuccessful(); + return mockReconnectStrategy; + } + + + private void shutdownServer() throws InterruptedException, ExecutionException { + // Shutdown server + server.channel().close().get(); + // Closing server channel does not close established connections, eventLoop has to be closed as well to simulate dropped session + serverLoopGroup.shutdownGracefully().get(); + } + + private SimpleDispatcher getServerDispatcher(final Promise p) { + return new SimpleDispatcher(new SessionNegotiatorFactory() { + + @Override + public SessionNegotiator getSessionNegotiator(final SessionListenerFactory factory, + final Channel channel, final Promise promise) { + p.setSuccess(true); + return new SimpleSessionNegotiator(promise, channel); + } + }, null, serverLoopGroup); + } + } diff --git a/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/SimpleDispatcher.java b/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/SimpleDispatcher.java index 12aac9ecc5..d83738520c 100644 --- a/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/SimpleDispatcher.java +++ b/opendaylight/commons/protocol-framework/src/test/java/org/opendaylight/protocol/framework/SimpleDispatcher.java @@ -54,6 +54,10 @@ public class SimpleDispatcher extends AbstractDispatcher createReconnectingClient(final InetSocketAddress address, final ReconnectStrategyFactory strategy, final SessionListenerFactory listenerFactory) { + return super.createReconnectingClient(address, strategy, new SimplePipelineInitializer(listenerFactory)); + } + public ChannelFuture createServer(final InetSocketAddress address, final SessionListenerFactory listenerFactory) { return super.createServer(address, new SimplePipelineInitializer(listenerFactory)); } diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/mapping/CodecRegistryProvider.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/mapping/CodecRegistryProvider.java index ec46219aaf..6050f7c070 100644 --- a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/mapping/CodecRegistryProvider.java +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/mapping/CodecRegistryProvider.java @@ -14,7 +14,7 @@ import org.opendaylight.yangtools.sal.binding.generator.api.ClassLoadingStrategy import org.opendaylight.yangtools.sal.binding.generator.impl.RuntimeGeneratedMappingServiceImpl; import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMappingService; import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; import org.osgi.framework.BundleContext; /** @@ -31,7 +31,7 @@ public class CodecRegistryProvider implements AutoCloseable { public CodecRegistryProvider(final ClassLoadingStrategy classLoadingStrategy, final BundleContext context) { service = new RuntimeGeneratedMappingServiceImpl(CLASS_POOL, classLoadingStrategy); registration = OsgiRegistrationUtil.registerService(context, service, - SchemaServiceListener.class, BindingIndependentMappingService.class); + SchemaContextListener.class, BindingIndependentMappingService.class); } public CodecRegistry getCodecRegistry() { diff --git a/opendaylight/config/config-netty-config/pom.xml b/opendaylight/config/config-netty-config/pom.xml new file mode 100644 index 0000000000..8dc31dcc4e --- /dev/null +++ b/opendaylight/config/config-netty-config/pom.xml @@ -0,0 +1,46 @@ + + + + + 4.0.0 + + org.opendaylight.controller + config-subsystem + 0.2.5-SNAPSHOT + + config-netty-config + Configuration files for sal-rest-connector + jar + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-artifacts + + attach-artifact + + package + + + + ${project.build.directory}/classes/initial/00-netty.xml + xml + config + + + + + + + + + diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/00-netty.xml b/opendaylight/config/config-netty-config/src/main/resources/initial/00-netty.xml similarity index 100% rename from opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/00-netty.xml rename to opendaylight/config/config-netty-config/src/main/resources/initial/00-netty.xml diff --git a/opendaylight/config/pom.xml b/opendaylight/config/pom.xml index 66bb01f051..26fac47e71 100644 --- a/opendaylight/config/pom.xml +++ b/opendaylight/config/pom.xml @@ -39,6 +39,7 @@ shutdown-impl netconf-config-dispatcher config-module-archetype + config-netty-config diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index 541c1300f3..4d0770f8cb 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -745,7 +745,7 @@ generate-resources ${project.build.directory}/configuration - sal-rest-connector-config + sal-rest-connector-config,config-netty-config,md-sal-config,netconf-config,toaster-config,netconf-connector-config **\/*.xml true false @@ -1034,6 +1034,26 @@ org.opendaylight.controller sal-rest-connector-config + + org.opendaylight.controller + config-netty-config + + + org.opendaylight.controller + md-sal-config + + + org.opendaylight.controller + netconf-config + + + org.opendaylight.controller + netconf-connector-config + + + org.opendaylight.controller.samples + toaster-config + org.opendaylight.controller sal-rest-docgen @@ -1318,7 +1338,7 @@ generate-resources ${project.build.directory}/configuration - sal-rest-connector-config + sal-rest-connector-config,config-netty-config,md-sal-config,netconf-config,toaster-config,netconf-connector-config **\/*.xml true false diff --git a/opendaylight/md-sal/md-sal-config/pom.xml b/opendaylight/md-sal/md-sal-config/pom.xml new file mode 100644 index 0000000000..2e19b5a60c --- /dev/null +++ b/opendaylight/md-sal/md-sal-config/pom.xml @@ -0,0 +1,46 @@ + + + + + 4.0.0 + + org.opendaylight.controller + sal-parent + 1.1-SNAPSHOT + + md-sal-config + Configuration files for md-sal + jar + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-artifacts + + attach-artifact + + package + + + + ${project.build.directory}/classes/initial/01-md-sal.xml + xml + config + + + + + + + + + diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/01-md-sal.xml b/opendaylight/md-sal/md-sal-config/src/main/resources/initial/01-md-sal.xml similarity index 100% rename from opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/01-md-sal.xml rename to opendaylight/md-sal/md-sal-config/src/main/resources/initial/01-md-sal.xml diff --git a/opendaylight/md-sal/pom.xml b/opendaylight/md-sal/pom.xml index 3f02765aff..02fbde8f18 100644 --- a/opendaylight/md-sal/pom.xml +++ b/opendaylight/md-sal/pom.xml @@ -32,6 +32,9 @@ sal-binding-util + + md-sal-config + samples @@ -69,6 +72,9 @@ sal-distributed-datastore + + sal-dom-xsql + sal-test-model diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/binding/impl/RuntimeMappingModule.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/binding/impl/RuntimeMappingModule.java index 823a4d9f32..b0c2d742e2 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/binding/impl/RuntimeMappingModule.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/binding/impl/RuntimeMappingModule.java @@ -23,7 +23,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMappingService; import org.opendaylight.yangtools.yang.data.impl.codec.CodecRegistry; import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.slf4j.Logger; @@ -74,7 +74,7 @@ public final class RuntimeMappingModule extends } final RuntimeGeneratedMappingServiceImpl service = new RuntimeGeneratedMappingServiceImpl(SingletonHolder.CLASS_POOL); - bundleContext.registerService(SchemaServiceListener.class, service, new Hashtable()); + bundleContext.registerService(SchemaContextListener.class, service, new Hashtable()); return service; } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedDataBroker.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedDataBroker.java index 15e4a466cf..2d81b6022d 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedDataBroker.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedDataBroker.java @@ -41,7 +41,6 @@ import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMapping import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,14 +57,14 @@ public abstract class AbstractForwardedDataBroker implements Delegator schemaListenerRegistration; + private final ListenerRegistration schemaListenerRegistration; protected AbstractForwardedDataBroker(final DOMDataBroker domDataBroker, final BindingIndependentMappingService mappingService,final SchemaService schemaService) { this.domDataBroker = domDataBroker; this.mappingService = mappingService; this.codec = new BindingToNormalizedNodeCodec(mappingService); - this.schemaListenerRegistration = schemaService.registerSchemaServiceListener(this); + this.schemaListenerRegistration = schemaService.registerSchemaContextListener(this); } protected BindingToNormalizedNodeCodec getCodec() { diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/MountPointManagerImpl.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/MountPointManagerImpl.java index df09f78620..05651bfabe 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/MountPointManagerImpl.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/MountPointManagerImpl.java @@ -9,6 +9,7 @@ package org.opendaylight.controller.sal.binding.impl; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; import org.opendaylight.controller.md.sal.binding.util.AbstractBindingSalProviderInstance; import org.opendaylight.controller.sal.binding.api.mount.MountProviderInstance; @@ -20,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; public class MountPointManagerImpl implements MountProviderService { @@ -82,7 +84,7 @@ public class MountPointManagerImpl implements MountProviderService { RpcProviderRegistryImpl rpcRegistry = new RpcProviderRegistryImpl("mount"); NotificationBrokerImpl notificationBroker = new NotificationBrokerImpl(getNotificationExecutor()); DataBrokerImpl dataBroker = new DataBrokerImpl(); - dataBroker.setExecutor(getDataCommitExecutor()); + dataBroker.setExecutor(MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor())); BindingMountPointImpl mountInstance = new BindingMountPointImpl(path, rpcRegistry, notificationBroker, dataBroker); mountPoints.putIfAbsent(path, mountInstance); diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/Bug1333DataChangeListenerTest.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/Bug1333DataChangeListenerTest.java new file mode 100644 index 0000000000..60d56db581 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/impl/test/Bug1333DataChangeListenerTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.md.sal.binding.impl.test; + +import static org.opendaylight.controller.md.sal.binding.test.AssertCollections.assertContains; +import static org.opendaylight.controller.md.sal.binding.test.AssertCollections.assertEmpty; +import static org.opendaylight.controller.md.sal.binding.test.AssertCollections.assertNotContains; +import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.TOP_FOO_KEY; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.USES_ONE_KEY; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.USES_TWO_KEY; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.complexUsesAugment; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.path; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.top; +import static org.opendaylight.controller.md.sal.test.model.util.ListsBindingUtils.topLevelList; + +import org.junit.Test; +import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction; +import org.opendaylight.controller.md.sal.binding.test.AbstractDataChangeListenerTest; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; +import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.augment.rev140709.TreeComplexUsesAugment; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * + * This testsuite tries to replicate bug 1333 and tests regresion of it + * using test-model with similar construction as one reported. + * + * + * See https://bugs.opendaylight.org/show_bug.cgi?id=1333 for Bug Description + * + */ +public class Bug1333DataChangeListenerTest extends AbstractDataChangeListenerTest{ + + private static final InstanceIdentifier TOP_PATH = InstanceIdentifier.create(Top.class); + + private static final InstanceIdentifier AUGMENT_WILDCARD = TOP_PATH.child(TopLevelList.class).augmentation( + TreeComplexUsesAugment.class); + + public void writeTopWithListItem(final LogicalDatastoreType store) { + ReadWriteTransaction tx = getDataBroker().newReadWriteTransaction(); + Top topItem = top(topLevelList(TOP_FOO_KEY, complexUsesAugment(USES_ONE_KEY, USES_TWO_KEY))); + tx.put(store, TOP_PATH, topItem); + assertCommit(tx.submit()); + } + + public void deleteItem(final LogicalDatastoreType store, final InstanceIdentifier path) { + ReadWriteTransaction tx = getDataBroker().newReadWriteTransaction(); + tx.delete(store, path); + assertCommit(tx.submit()); + } + + @Test + public void writeTopWithListItemAugmentedListenTopSubtree() { + TestListener listener = createListener(CONFIGURATION,TOP_PATH, DataChangeScope.SUBTREE); + listener.startCapture(); + + writeTopWithListItem(CONFIGURATION); + + AsyncDataChangeEvent, DataObject> event = listener.event(); + + assertContains(event.getCreatedData(), TOP_PATH); + assertContains(event.getCreatedData(), path(TOP_FOO_KEY)); + assertContains(event.getCreatedData(), path(TOP_FOO_KEY, TreeComplexUsesAugment.class)); + assertContains(event.getCreatedData(), path(TOP_FOO_KEY, USES_ONE_KEY)); + assertContains(event.getCreatedData(), path(TOP_FOO_KEY, USES_TWO_KEY)); + + assertEmpty(event.getUpdatedData()); + assertEmpty(event.getRemovedPaths()); + } + + @Test + public void writeTopWithListItemAugmentedListenAugmentSubtreeWildcarded() { + TestListener listener = createListener(CONFIGURATION,AUGMENT_WILDCARD, DataChangeScope.SUBTREE); + listener.startCapture(); + writeTopWithListItem(CONFIGURATION); + + AsyncDataChangeEvent, DataObject> event = listener.event(); + + /* + * Event should not contain parent nodes + */ + assertNotContains(event.getCreatedData(), TOP_PATH, path(TOP_FOO_KEY)); + + assertContains(event.getCreatedData(), path(TOP_FOO_KEY, TreeComplexUsesAugment.class)); + assertContains(event.getCreatedData(), path(TOP_FOO_KEY, USES_ONE_KEY)); + assertContains(event.getCreatedData(), path(TOP_FOO_KEY, USES_TWO_KEY)); + + assertEmpty(event.getUpdatedData()); + assertEmpty(event.getRemovedPaths()); + } + + @Test + public void deleteAugmentChildListenTopSubtree() { + writeTopWithListItem(CONFIGURATION); + TestListener listener = createListener(CONFIGURATION, TOP_PATH, DataChangeScope.SUBTREE); + InstanceIdentifier deletePath = path(TOP_FOO_KEY,USES_ONE_KEY); + deleteItem(CONFIGURATION,deletePath); + + AsyncDataChangeEvent, DataObject> event = listener.event(); + + + assertEmpty(event.getCreatedData()); + + assertContains(event.getRemovedPaths(), deletePath); + + assertContains(event.getUpdatedData(), TOP_PATH); + assertContains(event.getUpdatedData(), path(TOP_FOO_KEY)); + assertContains(event.getUpdatedData(), path(TOP_FOO_KEY, TreeComplexUsesAugment.class)); + + assertNotContains(event.getCreatedData(), path(TOP_FOO_KEY, USES_TWO_KEY)); + } + + @Test + public void deleteAugmentChildListenAugmentSubtreeWildcarded() { + writeTopWithListItem(CONFIGURATION); + + TestListener listener = createListener(CONFIGURATION, AUGMENT_WILDCARD, DataChangeScope.SUBTREE); + InstanceIdentifier deletePath = path(TOP_FOO_KEY,USES_ONE_KEY); + deleteItem(CONFIGURATION,deletePath); + AsyncDataChangeEvent, DataObject> event = listener.event(); + + assertEmpty(event.getCreatedData()); + + assertContains(event.getUpdatedData(), path(TOP_FOO_KEY, TreeComplexUsesAugment.class)); + + /* + * Event should not contain parent nodes + */ + assertNotContains(event.getUpdatedData(), TOP_PATH, path(TOP_FOO_KEY)); + + assertContains(event.getRemovedPaths(), deletePath); + } + +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/test/DataBrokerTestCustomizer.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/test/DataBrokerTestCustomizer.java index 79aa6b634b..e0f6f3546f 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/test/DataBrokerTestCustomizer.java +++ b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/md/sal/binding/test/DataBrokerTestCustomizer.java @@ -47,13 +47,13 @@ public class DataBrokerTestCustomizer { public DOMStore createConfigurationDatastore() { InMemoryDOMDataStore store = new InMemoryDOMDataStore("CFG", MoreExecutors.sameThreadExecutor()); - schemaService.registerSchemaServiceListener(store); + schemaService.registerSchemaContextListener(store); return store; } public DOMStore createOperationalDatastore() { InMemoryDOMDataStore store = new InMemoryDOMDataStore("OPER", MoreExecutors.sameThreadExecutor()); - schemaService.registerSchemaServiceListener(store); + schemaService.registerSchemaContextListener(store); return store; } diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/BindingTestContext.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/BindingTestContext.java index e82c9d385d..deb4a8aeca 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/BindingTestContext.java +++ b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/BindingTestContext.java @@ -144,8 +144,8 @@ public class BindingTestContext implements AutoCloseable { biCompatibleBroker = new BackwardsCompatibleDataBroker(newDOMDataBroker,mockSchemaService); - mockSchemaService.registerSchemaServiceListener(configStore); - mockSchemaService.registerSchemaServiceListener(operStore); + mockSchemaService.registerSchemaContextListener(configStore); + mockSchemaService.registerSchemaContextListener(operStore); biDataLegacyBroker = biCompatibleBroker; } @@ -246,7 +246,7 @@ public class BindingTestContext implements AutoCloseable { public void startBindingToDomMappingService() { checkState(classPool != null, "ClassPool needs to be present"); mappingServiceImpl = new RuntimeGeneratedMappingServiceImpl(classPool); - mockSchemaService.registerSchemaServiceListener(mappingServiceImpl); + mockSchemaService.registerSchemaContextListener(mappingServiceImpl); } private void updateYangSchema(final ImmutableSet moduleInfos) { diff --git a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/MockSchemaService.java b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/MockSchemaService.java index c8acbcd994..63a4ffb23a 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/MockSchemaService.java +++ b/opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/MockSchemaService.java @@ -13,14 +13,14 @@ import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.concepts.util.ListenerRegistry; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; @SuppressWarnings("deprecation") public final class MockSchemaService implements SchemaService, SchemaContextProvider { private SchemaContext schemaContext; - ListenerRegistry listeners = ListenerRegistry.create(); + ListenerRegistry listeners = ListenerRegistry.create(); @Override public void addModule(final Module module) { @@ -38,8 +38,8 @@ public final class MockSchemaService implements SchemaService, SchemaContextProv } @Override - public ListenerRegistration registerSchemaServiceListener( - final SchemaServiceListener listener) { + public ListenerRegistration registerSchemaContextListener( + final SchemaContextListener listener) { return listeners.register(listener); } @@ -55,8 +55,8 @@ public final class MockSchemaService implements SchemaService, SchemaContextProv public synchronized void changeSchema(final SchemaContext newContext) { schemaContext = newContext; - for (ListenerRegistration listener : listeners) { + for (ListenerRegistration listener : listeners) { listener.getInstance().onGlobalContextUpdated(schemaContext); } } -} \ No newline at end of file +} diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreFactory.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreFactory.java index 692d1b4954..6d87271f00 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreFactory.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/datastore/DistributedDataStoreFactory.java @@ -20,7 +20,7 @@ public class DistributedDataStoreFactory { new DistributedDataStore(actorSystem, name, new ClusterWrapperImpl(actorSystem),config ); ShardStrategyFactory.setConfiguration(config); schemaService - .registerSchemaServiceListener(dataStore); + .registerSchemaContextListener(dataStore); return dataStore; } diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/CarsModel.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/CarsModel.java index 57df20172d..6860872b75 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/CarsModel.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/CarsModel.java @@ -8,6 +8,7 @@ package org.opendaylight.controller.md.cluster.datastore.model; +import java.math.BigInteger; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; @@ -42,14 +43,14 @@ public class CarsModel { MapEntryNode altima = ImmutableNodes.mapEntryBuilder(CAR_QNAME, CAR_NAME_QNAME, "altima") .withChild(ImmutableNodes.leafNode(CAR_NAME_QNAME, "altima")) - .withChild(ImmutableNodes.leafNode(CAR_PRICE_QNAME, 1000)) + .withChild(ImmutableNodes.leafNode(CAR_PRICE_QNAME, new BigInteger("1000"))) .build(); // Create an entry for the car accord MapEntryNode honda = ImmutableNodes.mapEntryBuilder(CAR_QNAME, CAR_NAME_QNAME, "accord") .withChild(ImmutableNodes.leafNode(CAR_NAME_QNAME, "accord")) - .withChild(ImmutableNodes.leafNode(CAR_PRICE_QNAME, 2000)) + .withChild(ImmutableNodes.leafNode(CAR_PRICE_QNAME, new BigInteger("2000"))) .build(); cars.withChild(altima); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/PeopleModel.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/PeopleModel.java index 1b4020af43..e637920e78 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/PeopleModel.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/PeopleModel.java @@ -42,14 +42,14 @@ public class PeopleModel { MapEntryNode jack = ImmutableNodes.mapEntryBuilder(PERSON_QNAME, PERSON_NAME_QNAME, "jack") .withChild(ImmutableNodes.leafNode(PERSON_NAME_QNAME, "jack")) - .withChild(ImmutableNodes.leafNode(PERSON_AGE_QNAME, 100)) + .withChild(ImmutableNodes.leafNode(PERSON_AGE_QNAME, 100L)) .build(); // Create an entry for the person jill MapEntryNode jill = ImmutableNodes.mapEntryBuilder(PERSON_QNAME, PERSON_NAME_QNAME, "jill") .withChild(ImmutableNodes.leafNode(PERSON_NAME_QNAME, "jill")) - .withChild(ImmutableNodes.leafNode(PERSON_AGE_QNAME, 200)) + .withChild(ImmutableNodes.leafNode(PERSON_AGE_QNAME, 200L)) .build(); cars.withChild(jack); diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/SampleModelsTest.java b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/SampleModelsTest.java index d8fefcd986..be8713c702 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/SampleModelsTest.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/md/cluster/datastore/model/SampleModelsTest.java @@ -18,45 +18,45 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; public class SampleModelsTest { @Test public void testPeopleModel(){ - NormalizedNode expected = PeopleModel.create(); + final NormalizedNode expected = PeopleModel.create(); - NormalizedNodeMessages.Container node = + final NormalizedNodeMessages.Container node = new NormalizedNodeToNodeCodec(SchemaContextHelper.full()) .encode(YangInstanceIdentifier.of(PeopleModel.BASE_QNAME), expected); - NormalizedNodeMessages.Node normalizedNode = + final NormalizedNodeMessages.Node normalizedNode = node.getNormalizedNode(); - NormalizedNode actual = new NormalizedNodeToNodeCodec(SchemaContextHelper.full()).decode(YangInstanceIdentifier.of(PeopleModel.BASE_QNAME), + final NormalizedNode actual = new NormalizedNodeToNodeCodec(SchemaContextHelper.full()).decode(YangInstanceIdentifier.of(PeopleModel.BASE_QNAME), normalizedNode); - Assert.assertEquals(expected.toString(), actual.toString()); + Assert.assertEquals(expected, actual); } @Test public void testCarsModel(){ - NormalizedNode expected = CarsModel.create(); + final NormalizedNode expected = CarsModel.create(); - NormalizedNodeMessages.Container node = + final NormalizedNodeMessages.Container node = new NormalizedNodeToNodeCodec(SchemaContextHelper.full()) .encode(YangInstanceIdentifier.of(CarsModel.BASE_QNAME), expected); - NormalizedNodeMessages.Node normalizedNode = + final NormalizedNodeMessages.Node normalizedNode = node.getNormalizedNode(); - NormalizedNode actual = new NormalizedNodeToNodeCodec(SchemaContextHelper.full()).decode( + final NormalizedNode actual = new NormalizedNodeToNodeCodec(SchemaContextHelper.full()).decode( YangInstanceIdentifier.of(CarsModel.BASE_QNAME), normalizedNode); - Assert.assertEquals(expected.toString(), actual.toString()); + Assert.assertEquals(expected, actual); } } diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/model/SchemaService.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/model/SchemaService.java index 34e5b1b803..c3e979c536 100644 --- a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/model/SchemaService.java +++ b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/sal/core/api/model/SchemaService.java @@ -11,7 +11,7 @@ import org.opendaylight.controller.sal.core.api.BrokerService; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; public interface SchemaService extends BrokerService { @@ -42,5 +42,11 @@ public interface SchemaService extends BrokerService { */ SchemaContext getGlobalContext(); - ListenerRegistration registerSchemaServiceListener(SchemaServiceListener listener); + /** + * Register a listener for changes in schema context. + * + * @param listener Listener which should be registered + * @return Listener registration handle + */ + ListenerRegistration registerSchemaContextListener(SchemaContextListener listener); } diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/DomBrokerImplModule.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/DomBrokerImplModule.java index 17b78f4ebd..f1f16cd635 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/DomBrokerImplModule.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/DomBrokerImplModule.java @@ -91,7 +91,7 @@ public final class DomBrokerImplModule extends org.opendaylight.controller.confi wrappedStore.changeDelegate(legacyStore); wrappedStore.setValidationEnabled(false); - schemaService.registerSchemaServiceListener(wrappedStore); + schemaService.registerSchemaContextListener(wrappedStore); dataService.registerConfigurationReader(rootPath, wrappedStore); dataService.registerCommitHandler(rootPath, wrappedStore); diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/DomInmemoryDataBrokerModule.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/DomInmemoryDataBrokerModule.java index 69b17ee3c4..667c0fc282 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/DomInmemoryDataBrokerModule.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/DomInmemoryDataBrokerModule.java @@ -50,7 +50,7 @@ public final class DomInmemoryDataBrokerModule extends //we will default to InMemoryDOMDataStore creation operStore = new InMemoryDOMDataStore("DOM-OPER", storeExecutor); //here we will register the SchemaContext listener - getSchemaServiceDependency().registerSchemaServiceListener((InMemoryDOMDataStore)operStore); + getSchemaServiceDependency().registerSchemaContextListener((InMemoryDOMDataStore)operStore); } DOMStore configStore = getConfigDataStoreDependency(); @@ -58,7 +58,7 @@ public final class DomInmemoryDataBrokerModule extends //we will default to InMemoryDOMDataStore creation configStore = new InMemoryDOMDataStore("DOM-CFG", storeExecutor); //here we will register the SchemaContext listener - getSchemaServiceDependency().registerSchemaServiceListener((InMemoryDOMDataStore)configStore); + getSchemaServiceDependency().registerSchemaContextListener((InMemoryDOMDataStore)configStore); } ImmutableMap datastores = ImmutableMap . builder().put(LogicalDatastoreType.OPERATIONAL, operStore) diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/SchemaServiceImplSingletonModule.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/SchemaServiceImplSingletonModule.java index fbc418dc2a..62b026430a 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/SchemaServiceImplSingletonModule.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/config/yang/md/sal/dom/impl/SchemaServiceImplSingletonModule.java @@ -13,7 +13,7 @@ import org.opendaylight.yangtools.concepts.Delegator; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,8 +90,8 @@ org.opendaylight.controller.config.yang.md.sal.dom.impl.AbstractSchemaServiceImp } @Override - public ListenerRegistration registerSchemaServiceListener(final SchemaServiceListener arg0) { - return delegate.registerSchemaServiceListener(arg0); + public ListenerRegistration registerSchemaContextListener(final SchemaContextListener arg0) { + return delegate.registerSchemaContextListener(arg0); } @Override diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/compat/BackwardsCompatibleDataBroker.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/compat/BackwardsCompatibleDataBroker.java index e8f8da53c9..dc122cfdc2 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/compat/BackwardsCompatibleDataBroker.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/compat/BackwardsCompatibleDataBroker.java @@ -1,3 +1,10 @@ +/* + * 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.compat; import javax.annotation.concurrent.ThreadSafe; @@ -21,18 +28,17 @@ import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; @ThreadSafe public class BackwardsCompatibleDataBroker implements DataProviderService { private final DOMDataBroker backingBroker; private volatile DataNormalizer normalizer; - private final ListenerRegistration schemaReg; + private final ListenerRegistration schemaReg; public BackwardsCompatibleDataBroker(final DOMDataBroker newBiDataImpl, final SchemaService schemaService) { backingBroker = newBiDataImpl; - schemaReg = schemaService.registerSchemaServiceListener(new SchemaListener()); + schemaReg = schemaService.registerSchemaContextListener(new SchemaListener()); } @Override diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/BackwardsCompatibleMountPoint.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/BackwardsCompatibleMountPoint.java index d837d75ddc..61ea47e39b 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/BackwardsCompatibleMountPoint.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/BackwardsCompatibleMountPoint.java @@ -72,7 +72,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgum import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; public class BackwardsCompatibleMountPoint implements MountProvisionInstance, SchemaContextProvider, SchemaService { @@ -83,7 +83,7 @@ public class BackwardsCompatibleMountPoint implements MountProvisionInstance, Sc private final NotificationPublishService notificationPublishService; private final RpcProvisionRegistry rpcs; - private final ListenerRegistry schemaListenerRegistry = new ListenerRegistry<>(); + private final ListenerRegistry schemaListenerRegistry = new ListenerRegistry<>(); private SchemaContext schemaContext; @@ -154,7 +154,7 @@ public class BackwardsCompatibleMountPoint implements MountProvisionInstance, Sc } @Override - public ListenerRegistration registerSchemaServiceListener(final SchemaServiceListener listener) { + public ListenerRegistration registerSchemaContextListener(final SchemaContextListener listener) { return schemaListenerRegistry.register(listener); } @@ -275,7 +275,7 @@ public class BackwardsCompatibleMountPoint implements MountProvisionInstance, Sc @Override public void setSchemaContext(final SchemaContext schemaContext) { this.schemaContext = schemaContext; - for (ListenerRegistration schemaServiceListenerListenerRegistration : schemaListenerRegistry.getListeners()) { + for (ListenerRegistration schemaServiceListenerListenerRegistration : schemaListenerRegistry.getListeners()) { schemaServiceListenerListenerRegistration.getInstance().onGlobalContextUpdated(schemaContext); } } diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/GlobalBundleScanningSchemaServiceImpl.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/GlobalBundleScanningSchemaServiceImpl.java index d8174c312a..82637327f6 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/GlobalBundleScanningSchemaServiceImpl.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/GlobalBundleScanningSchemaServiceImpl.java @@ -23,7 +23,7 @@ import org.opendaylight.yangtools.concepts.Registration; import org.opendaylight.yangtools.concepts.util.ListenerRegistry; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; import org.opendaylight.yangtools.yang.parser.impl.util.URLSchemaContextResolver; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -40,15 +40,15 @@ import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvider, SchemaService, ServiceTrackerCustomizer, AutoCloseable { +public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvider, SchemaService, ServiceTrackerCustomizer, AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(GlobalBundleScanningSchemaServiceImpl.class); - private final ListenerRegistry listeners = new ListenerRegistry<>(); + private final ListenerRegistry listeners = new ListenerRegistry<>(); private final URLSchemaContextResolver contextResolver = new URLSchemaContextResolver(); private final BundleScanner scanner = new BundleScanner(); private final BundleContext context; - private ServiceTracker listenerTracker; + private ServiceTracker listenerTracker; private BundleTracker> bundleTracker; private boolean starting = true; private static GlobalBundleScanningSchemaServiceImpl instance; @@ -81,7 +81,7 @@ public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvi public void start() { checkState(context != null); - listenerTracker = new ServiceTracker<>(context, SchemaServiceListener.class, GlobalBundleScanningSchemaServiceImpl.this); + listenerTracker = new ServiceTracker<>(context, SchemaContextListener.class, GlobalBundleScanningSchemaServiceImpl.this); bundleTracker = new BundleTracker<>(context, BundleEvent.RESOLVED | BundleEvent.UNRESOLVED, scanner); bundleTracker.open(); listenerTracker.open(); @@ -115,7 +115,7 @@ public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvi } @Override - public synchronized ListenerRegistration registerSchemaServiceListener(final SchemaServiceListener listener) { + public synchronized ListenerRegistration registerSchemaContextListener(final SchemaContextListener listener) { Optional potentialCtx = contextResolver.getSchemaContext(); if(potentialCtx.isPresent()) { listener.onGlobalContextUpdated(potentialCtx.get()); @@ -137,7 +137,7 @@ public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvi private synchronized void updateContext(final SchemaContext snapshot) { Object[] services = listenerTracker.getServices(); - for (ListenerRegistration listener : listeners) { + for (ListenerRegistration listener : listeners) { try { listener.getInstance().onGlobalContextUpdated(snapshot); } catch (Exception e) { @@ -146,7 +146,7 @@ public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvi } if (services != null) { for (Object rawListener : services) { - SchemaServiceListener listener = (SchemaServiceListener) rawListener; + final SchemaContextListener listener = (SchemaContextListener) rawListener; try { listener.onGlobalContextUpdated(snapshot); } catch (Exception e) { @@ -213,9 +213,9 @@ public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvi } @Override - public synchronized SchemaServiceListener addingService(final ServiceReference reference) { + public synchronized SchemaContextListener addingService(final ServiceReference reference) { - SchemaServiceListener listener = context.getService(reference); + SchemaContextListener listener = context.getService(reference); SchemaContext _ctxContext = getGlobalContext(); if (getContext() != null && _ctxContext != null) { listener.onGlobalContextUpdated(_ctxContext); @@ -234,12 +234,12 @@ public class GlobalBundleScanningSchemaServiceImpl implements SchemaContextProvi } @Override - public void modifiedService(final ServiceReference reference, final SchemaServiceListener service) { + public void modifiedService(final ServiceReference reference, final SchemaContextListener service) { // NOOP } @Override - public void removedService(final ServiceReference reference, final SchemaServiceListener service) { + public void removedService(final ServiceReference reference, final SchemaContextListener service) { context.ungetService(reference); } } diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/osgi/SchemaServiceProxy.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/osgi/SchemaServiceProxy.java index 1d864eec5b..d8d2346a8c 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/osgi/SchemaServiceProxy.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/sal/dom/broker/osgi/SchemaServiceProxy.java @@ -8,10 +8,10 @@ package org.opendaylight.controller.sal.dom.broker.osgi; import org.opendaylight.controller.sal.core.api.model.SchemaService; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; import org.osgi.framework.ServiceReference; public class SchemaServiceProxy extends AbstractBrokerServiceProxy implements SchemaService { @@ -41,12 +41,9 @@ public class SchemaServiceProxy extends AbstractBrokerServiceProxy registerSchemaServiceListener(SchemaServiceListener listener) { - ListenerRegistration registration = getDelegate().registerSchemaServiceListener(listener); + public ListenerRegistration registerSchemaContextListener(SchemaContextListener listener) { + ListenerRegistration registration = getDelegate().registerSchemaContextListener(listener); addRegistration(registration); return registration; } - - - } diff --git a/opendaylight/md-sal/sal-dom-xsql/src/main/java/org/opendaylight/yang/gen/v1/http/netconfcentral/org/ns/xsql/rev140626/XSQLModule.java b/opendaylight/md-sal/sal-dom-xsql/src/main/java/org/opendaylight/yang/gen/v1/http/netconfcentral/org/ns/xsql/rev140626/XSQLModule.java index 399f49bd6b..59cdc76e7a 100644 --- a/opendaylight/md-sal/sal-dom-xsql/src/main/java/org/opendaylight/yang/gen/v1/http/netconfcentral/org/ns/xsql/rev140626/XSQLModule.java +++ b/opendaylight/md-sal/sal-dom-xsql/src/main/java/org/opendaylight/yang/gen/v1/http/netconfcentral/org/ns/xsql/rev140626/XSQLModule.java @@ -20,7 +20,7 @@ public class XSQLModule extends org.opendaylight.yang.gen.v1.http.netconfcentral @Override public java.lang.AutoCloseable createInstance() { XSQLAdapter xsqlAdapter = XSQLAdapter.getInstance(); - getSchemaServiceDependency().registerSchemaServiceListener(xsqlAdapter); + getSchemaServiceDependency().registerSchemaContextListener(xsqlAdapter); xsqlAdapter.setDataBroker(getAsyncDataBrokerDependency()); XSQLProvider p = new XSQLProvider(); p.buildXSQL(getDataBrokerDependency()); diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/config/yang/inmemory_datastore_provider/InMemoryConfigDataStoreProviderModule.java b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/config/yang/inmemory_datastore_provider/InMemoryConfigDataStoreProviderModule.java index 01a5989dcd..805608d479 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/config/yang/inmemory_datastore_provider/InMemoryConfigDataStoreProviderModule.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/config/yang/inmemory_datastore_provider/InMemoryConfigDataStoreProviderModule.java @@ -23,7 +23,7 @@ public class InMemoryConfigDataStoreProviderModule extends org.opendaylight.cont @Override public java.lang.AutoCloseable createInstance() { InMemoryDOMDataStore ids = new InMemoryDOMDataStore("DOM-CFG", MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor())); - getSchemaServiceDependency().registerSchemaServiceListener(ids); + getSchemaServiceDependency().registerSchemaContextListener(ids); return ids; } diff --git a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/config/yang/inmemory_datastore_provider/InMemoryOperationalDataStoreProviderModule.java b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/config/yang/inmemory_datastore_provider/InMemoryOperationalDataStoreProviderModule.java index b39c9bbbd8..f4795588ab 100644 --- a/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/config/yang/inmemory_datastore_provider/InMemoryOperationalDataStoreProviderModule.java +++ b/opendaylight/md-sal/sal-inmemory-datastore/src/main/java/org/opendaylight/controller/config/yang/inmemory_datastore_provider/InMemoryOperationalDataStoreProviderModule.java @@ -23,7 +23,7 @@ public class InMemoryOperationalDataStoreProviderModule extends org.opendaylight @Override public java.lang.AutoCloseable createInstance() { InMemoryDOMDataStore ids = new InMemoryDOMDataStore("DOM-OPER", MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor())); - getOperationalSchemaServiceDependency().registerSchemaServiceListener(ids); + getOperationalSchemaServiceDependency().registerSchemaContextListener(ids); return ids; } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java index 037bfb4a82..b75df80f4e 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java @@ -13,8 +13,10 @@ import static org.opendaylight.controller.config.api.JmxAttributeValidationExcep import java.io.File; import java.io.InputStream; import java.net.InetSocketAddress; +import java.util.List; import java.util.concurrent.ExecutorService; +import org.opendaylight.controller.config.api.JmxAttributeValidationException; import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfiguration; @@ -24,6 +26,7 @@ import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.controller.sal.connect.netconf.NetconfDevice; 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.sal.NetconfDeviceSalFacade; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.controller.sal.core.api.Broker; @@ -40,6 +43,8 @@ import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Optional; + /** * */ @@ -49,6 +54,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co private static AbstractCachingSchemaSourceProvider GLOBAL_NETCONF_SOURCE_PROVIDER = null; private BundleContext bundleContext; + private Optional userCapabilities; public NetconfConnectorModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { super(identifier, dependencyResolver); @@ -82,9 +88,11 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co checkNotNull(getPassword(), passwordJmxAttribute); } + userCapabilities = getUserCapabilities(); + } - private boolean isHostAddressPresent(Host address) { + private boolean isHostAddressPresent(final Host address) { return address.getDomainName() != null || address.getIpAddress() != null && (address.getIpAddress().getIpv4Address() != null || address.getIpAddress().getIpv6Address() != null); } @@ -98,10 +106,14 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co final Broker domBroker = getDomRegistryDependency(); final BindingAwareBroker bindingBroker = getBindingRegistryDependency(); - final RemoteDeviceHandler salFacade = new NetconfDeviceSalFacade(id, domBroker, bindingBroker, bundleContext, globalProcessingExecutor); + final RemoteDeviceHandler salFacade + = new NetconfDeviceSalFacade(id, domBroker, bindingBroker, bundleContext, globalProcessingExecutor); final NetconfDevice device = NetconfDevice.createNetconfDevice(id, getGlobalNetconfSchemaProvider(), globalProcessingExecutor, salFacade); - final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(id, device); + + final NetconfDeviceCommunicator listener = userCapabilities.isPresent() ? + new NetconfDeviceCommunicator(id, device, userCapabilities.get()) : new NetconfDeviceCommunicator(id, device); + final NetconfReconnectingClientConfiguration clientConfig = getClientConfig(listener); final NetconfClientDispatcher dispatcher = getClientDispatcherDependency(); @@ -116,6 +128,26 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co }; } + private Optional getUserCapabilities() { + if(getYangModuleCapabilities() == null) { + return Optional.absent(); + } + + final List capabilities = getYangModuleCapabilities().getCapability(); + if(capabilities == null || capabilities.isEmpty()) { + return Optional.absent(); + } + + final NetconfSessionCapabilities parsedOverrideCapabilities = NetconfSessionCapabilities.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," + + " configured non-module capabilities: " + parsedOverrideCapabilities.getNonModuleCaps(), + yangModuleCapabilitiesJmxAttribute); + + return Optional.of(parsedOverrideCapabilities); + } + private synchronized AbstractCachingSchemaSourceProvider getGlobalNetconfSchemaProvider() { if(GLOBAL_NETCONF_SOURCE_PROVIDER == null) { final String storageFile = "cache/schema"; @@ -175,8 +207,8 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co if(getAddress().getDomainName() != null) { return new InetSocketAddress(getAddress().getDomainName().getValue(), getPort().getValue()); } else { - IpAddress ipAddress = getAddress().getIpAddress(); - String ip = ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue(); + final IpAddress ipAddress = getAddress().getIpAddress(); + final String ip = ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue(); return new InetSocketAddress(ip, getPort().getValue()); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java index de4ac7ac18..07d3c08774 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java @@ -118,6 +118,7 @@ public final class NetconfDevice implements RemoteDevice { private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceCommunicator.class); private final RemoteDevice remoteDevice; + private final Optional overrideNetconfCapabilities; private final RemoteDeviceId id; private final Lock sessionLock = new ReentrantLock(); + private final Queue requests = new ArrayDeque<>(); + private NetconfClientSession session; + + public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, + final NetconfSessionCapabilities netconfSessionCapabilities) { + this(id, remoteDevice, Optional.of(netconfSessionCapabilities)); + } + public NetconfDeviceCommunicator(final RemoteDeviceId id, - final RemoteDevice remoteDevice) { + final RemoteDevice remoteDevice) { + this(id, remoteDevice, Optional.absent()); + } + + private NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, + final Optional overrideNetconfCapabilities) { this.id = id; this.remoteDevice = remoteDevice; + this.overrideNetconfCapabilities = overrideNetconfCapabilities; } - private final Queue requests = new ArrayDeque<>(); - private NetconfClientSession session; - @Override public void onSessionUp(final NetconfClientSession session) { sessionLock.lock(); @@ -68,10 +78,15 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, logger.debug("{}: Session established", id); this.session = session; - final NetconfSessionCapabilities netconfSessionCapabilities = + NetconfSessionCapabilities netconfSessionCapabilities = NetconfSessionCapabilities.fromNetconfSession(session); logger.trace("{}: Session advertised capabilities: {}", id, netconfSessionCapabilities); + if(overrideNetconfCapabilities.isPresent()) { + netconfSessionCapabilities = netconfSessionCapabilities.replaceModuleCaps(overrideNetconfCapabilities.get()); + logger.debug("{}: Session capabilities overridden, capabilities that will be used: {}", id, netconfSessionCapabilities); + } + remoteDevice.onRemoteSessionUp(netconfSessionCapabilities, this); } finally { @@ -223,7 +238,7 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, return; } - request.future.set( RpcResultBuilder.success( message ).build() ); + request.future.set( RpcResultBuilder.success( message ).build() ); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java index 8964a80228..1a7d90e9c0 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java @@ -8,6 +8,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -19,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class NetconfSessionCapabilities { + private static final class ParameterMatcher { private final Predicate predicate; private final int skipLength; @@ -57,10 +59,10 @@ public final class NetconfSessionCapabilities { }; private final Set moduleBasedCaps; - private final Set capabilities; + private final Set nonModuleCaps; - private NetconfSessionCapabilities(final Set capabilities, final Set moduleBasedCaps) { - this.capabilities = Preconditions.checkNotNull(capabilities); + private NetconfSessionCapabilities(final Set nonModuleCaps, final Set moduleBasedCaps) { + this.nonModuleCaps = Preconditions.checkNotNull(nonModuleCaps); this.moduleBasedCaps = Preconditions.checkNotNull(moduleBasedCaps); } @@ -68,30 +70,49 @@ public final class NetconfSessionCapabilities { return moduleBasedCaps; } - public boolean containsCapability(final String capability) { - return capabilities.contains(capability); + public Set getNonModuleCaps() { + return nonModuleCaps; + } + + public boolean containsNonModuleCapability(final String capability) { + return nonModuleCaps.contains(capability); } - public boolean containsCapability(final QName capability) { + public boolean containsModuleCapability(final QName capability) { return moduleBasedCaps.contains(capability); } @Override public String toString() { return Objects.toStringHelper(this) - .add("capabilities", capabilities) + .add("capabilities", nonModuleCaps) + .add("moduleBasedCapabilities", moduleBasedCaps) .add("rollback", isRollbackSupported()) .add("monitoring", isMonitoringSupported()) .toString(); } public boolean isRollbackSupported() { - return containsCapability(NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()); + return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()); + } + + public boolean isCandidateSupported() { + return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString()); } public boolean isMonitoringSupported() { - return containsCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING) - || containsCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); + return containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING) + || containsNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); + } + + public NetconfSessionCapabilities replaceModuleCaps(final NetconfSessionCapabilities netconfSessionModuleCapabilities) { + final Set moduleBasedCaps = Sets.newHashSet(netconfSessionModuleCapabilities.getModuleBasedCaps()); + + // Preserve monitoring module, since it indicates support for ietf-netconf-monitoring + if(containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING)) { + moduleBasedCaps.add(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING); + } + return new NetconfSessionCapabilities(getNonModuleCaps(), moduleBasedCaps); } public static NetconfSessionCapabilities fromNetconfSession(final NetconfClientSession session) { @@ -100,6 +121,7 @@ public final class NetconfSessionCapabilities { public static NetconfSessionCapabilities fromStrings(final Collection capabilities) { final Set moduleBasedCaps = new HashSet<>(); + final Set nonModuleCaps = Sets.newHashSet(capabilities); for (final String capability : capabilities) { final int qmark = capability.indexOf('?'); @@ -117,6 +139,7 @@ public final class NetconfSessionCapabilities { String revision = REVISION_PARAM.from(queryParams); if (revision != null) { moduleBasedCaps.add(QName.create(namespace, revision, moduleName)); + nonModuleCaps.remove(capability); continue; } @@ -136,8 +159,9 @@ public final class NetconfSessionCapabilities { // FIXME: do we really want to continue here? moduleBasedCaps.add(QName.create(namespace, revision, moduleName)); + nonModuleCaps.remove(capability); } - return new NetconfSessionCapabilities(ImmutableSet.copyOf(capabilities), ImmutableSet.copyOf(moduleBasedCaps)); + return new NetconfSessionCapabilities(ImmutableSet.copyOf(nonModuleCaps), ImmutableSet.copyOf(moduleBasedCaps)); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java index ee0c8b7217..f3a9acd630 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java @@ -42,7 +42,7 @@ final class NetconfDeviceDataBroker implements DOMDataBroker { @Override public DOMDataReadOnlyTransaction newReadOnlyTransaction() { - return new NetconfDeviceReadOnlyTx(rpc, normalizer); + return new NetconfDeviceReadOnlyTx(rpc, normalizer, id); } @Override @@ -52,8 +52,7 @@ final class NetconfDeviceDataBroker implements DOMDataBroker { @Override public DOMDataWriteTransaction newWriteOnlyTransaction() { - // FIXME detect if candidate is supported, true is hardcoded - return new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, true, netconfSessionPreferences.isRollbackSupported()); + return new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, netconfSessionPreferences.isCandidateSupported(), netconfSessionPreferences.isRollbackSupported()); } @Override diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java index 3248453baf..9ef44f6584 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java @@ -15,6 +15,7 @@ import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessag import com.google.common.base.Function; import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; @@ -22,6 +23,7 @@ import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizat import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.controller.sal.core.api.RpcImplementation; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; @@ -32,16 +34,19 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction { private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceReadOnlyTx.class); private final RpcImplementation rpc; private final DataNormalizer normalizer; + private final RemoteDeviceId id; - public NetconfDeviceReadOnlyTx(final RpcImplementation rpc, final DataNormalizer normalizer) { + public NetconfDeviceReadOnlyTx(final RpcImplementation rpc, final DataNormalizer normalizer, final RemoteDeviceId id) { this.rpc = rpc; this.normalizer = normalizer; + this.id = id; } public ListenableFuture>> readConfigurationData(final YangInstanceIdentifier path) { @@ -51,6 +56,8 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction return Futures.transform(future, new Function, Optional>>() { @Override public Optional> apply(final RpcResult result) { + checkReadSuccess(result, path); + final CompositeNode data = result.getResult().getFirstCompositeByName(NETCONF_DATA_QNAME); final CompositeNode node = (CompositeNode) findNode(data, path); @@ -61,6 +68,11 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction }); } + private void checkReadSuccess(final RpcResult result, final YangInstanceIdentifier path) { + LOG.warn("{}: Unable to read data: {}, errors: {}", id, path, result.getErrors()); + Preconditions.checkArgument(result.isSuccessful(), "%s: Unable to read data: %s, errors: %s", id, path, result.getErrors()); + } + private Optional> transform(final YangInstanceIdentifier path, final CompositeNode node) { if(node == null) { return Optional.absent(); @@ -68,7 +80,7 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction try { return Optional.>of(normalizer.toNormalized(path, node).getValue()); } catch (final Exception e) { - LOG.error("Unable to normalize data for {}, data: {}", path, node, e); + LOG.error("{}: Unable to normalize data for {}, data: {}", id, path, node, e); throw e; } } @@ -79,6 +91,8 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction return Futures.transform(future, new Function, Optional>>() { @Override public Optional> apply(final RpcResult result) { + checkReadSuccess(result, path); + final CompositeNode data = result.getResult().getFirstCompositeByName(NETCONF_DATA_QNAME); final CompositeNode node = (CompositeNode) findNode(data, path); @@ -123,7 +137,7 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction @Override public ListenableFuture>> read(final LogicalDatastoreType store, final YangInstanceIdentifier path) { - final YangInstanceIdentifier legacyPath = toLegacyPath(normalizer, path); + final YangInstanceIdentifier legacyPath = toLegacyPath(normalizer, path, id); switch (store) { case CONFIGURATION : { @@ -134,14 +148,14 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction } } - throw new IllegalArgumentException(String.format("Cannot read data %s for %s datastore, unknown datastore type", path, store)); + throw new IllegalArgumentException(String.format("%s, Cannot read data %s for %s datastore, unknown datastore type", id, path, store)); } - static YangInstanceIdentifier toLegacyPath(final DataNormalizer normalizer, final YangInstanceIdentifier path) { + static YangInstanceIdentifier toLegacyPath(final DataNormalizer normalizer, final YangInstanceIdentifier path, final RemoteDeviceId id) { try { return normalizer.toLegacy(path); } catch (final DataNormalizationException e) { - throw new IllegalArgumentException("Cannot normalize path " + path, e); + throw new IllegalArgumentException(id + ": Cannot normalize path " + path, e); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java index c8d9028210..87f5477d35 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java @@ -8,10 +8,11 @@ package org.opendaylight.controller.sal.connect.netconf.sal.tx; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.DISCARD_CHANGES_RPC_CONTENT; import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME; import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_QNAME; import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DEFAULT_OPERATION_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME; import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME; import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_QNAME; import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME; @@ -26,13 +27,14 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; -import javax.annotation.Nullable; +import java.util.concurrent.atomic.AtomicBoolean; import org.opendaylight.controller.md.sal.common.api.TransactionStatus; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; @@ -57,97 +59,114 @@ import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction { +public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction, FutureCallback> { private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceWriteOnlyTx.class); private final RemoteDeviceId id; private final RpcImplementation rpc; private final DataNormalizer normalizer; + private final boolean rollbackSupported; + private final boolean candidateSupported; private final CompositeNode targetNode; + // Allow commit to be called only once + private final AtomicBoolean finished = new AtomicBoolean(false); + public NetconfDeviceWriteOnlyTx(final RemoteDeviceId id, final RpcImplementation rpc, final DataNormalizer normalizer, final boolean candidateSupported, final boolean rollbackOnErrorSupported) { this.id = id; this.rpc = rpc; this.normalizer = normalizer; - this.targetNode = getTargetNode(candidateSupported); + + this.candidateSupported = candidateSupported; + this.targetNode = getTargetNode(this.candidateSupported); this.rollbackSupported = rollbackOnErrorSupported; } - // FIXME add logging - @Override public boolean cancel() { - if(isCommitted()) { + if(isFinished()) { return false; } return discardChanges(); } - private boolean isCommitted() { - // TODO 732 - return true; + private boolean isFinished() { + return finished.get(); } private boolean discardChanges() { - // TODO 732 + finished.set(true); + + if(candidateSupported) { + sendDiscardChanges(); + } return true; } // TODO should the edit operations be blocking ? + // TODO should the discard-changes operations be blocking ? @Override public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { + checkNotFinished(); Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store); try { - final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path); + final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id); final CompositeNode legacyData = normalizer.toLegacy(path, data); - sendEditRpc(createEditConfigStructure(legacyPath, Optional.of(ModifyAction.REPLACE), Optional.fromNullable(legacyData)), Optional.of(ModifyAction.NONE)); + sendEditRpc( + createEditConfigStructure(legacyPath, Optional.of(ModifyAction.REPLACE), Optional.fromNullable(legacyData)), Optional.of(ModifyAction.NONE)); } catch (final ExecutionException e) { - LOG.warn("Error putting data to {}, data: {}, discarding changes", path, data, e); + LOG.warn("{}: Error putting data to {}, data: {}, discarding changes", id, path, data, e); discardChanges(); - throw new RuntimeException("Error while replacing " + path, e); + throw new RuntimeException(id + ": Error while replacing " + path, e); } } + private void checkNotFinished() { + Preconditions.checkState(isFinished() == false, "%s: Transaction %s already finished", id, getIdentifier()); + } + @Override public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { - Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store); + checkNotFinished(); + Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store); try { - final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path); + final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id); final CompositeNode legacyData = normalizer.toLegacy(path, data); sendEditRpc( createEditConfigStructure(legacyPath, Optional. absent(), Optional.fromNullable(legacyData)), Optional. absent()); } catch (final ExecutionException e) { - LOG.warn("Error merging data to {}, data: {}, discarding changes", path, data, e); + LOG.warn("{}: Error merging data to {}, data: {}, discarding changes", id, path, data, e); discardChanges(); - throw new RuntimeException("Error while merging " + path, e); + throw new RuntimeException(id + ": Error while merging " + path, e); } } @Override public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) { - Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store); + checkNotFinished(); + Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store); try { - sendEditRpc(createEditConfigStructure(NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path), Optional.of(ModifyAction.DELETE), Optional.absent()), Optional.of(ModifyAction.NONE)); + sendEditRpc( + createEditConfigStructure(NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id), Optional.of(ModifyAction.DELETE), Optional.absent()), Optional.of(ModifyAction.NONE)); } catch (final ExecutionException e) { - LOG.warn("Error deleting data {}, discarding changes", path, e); + LOG.warn("{}: Error deleting data {}, discarding changes", id, path, e); discardChanges(); - throw new RuntimeException("Error while deleting " + path, e); + throw new RuntimeException(id + ": Error while deleting " + path, e); } } @Override public CheckedFuture submit() { final ListenableFuture commmitFutureAsVoid = Futures.transform(commit(), new Function, Void>() { - @Nullable @Override - public Void apply(@Nullable final RpcResult input) { + public Void apply(final RpcResult input) { return null; } }); @@ -162,25 +181,46 @@ public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction { @Override public ListenableFuture> commit() { - // FIXME do not allow commit if closed or failed + checkNotFinished(); + finished.set(true); - final ListenableFuture> rpcResult = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, getCommitRequest()); - return Futures.transform(rpcResult, new Function, RpcResult>() { - @Override - public RpcResult apply(@Nullable final RpcResult input) { - if(input.isSuccessful()) { - return RpcResultBuilder.success(TransactionStatus.COMMITED).build(); - } else { - final RpcResultBuilder failed = RpcResultBuilder.failed(); - for (final RpcError rpcError : input.getErrors()) { - failed.withError(rpcError.getErrorType(), rpcError.getTag(), rpcError.getMessage(), rpcError.getApplicationTag(), rpcError.getInfo(), rpcError.getCause()); + if(candidateSupported == false) { + return Futures.immediateFuture(RpcResultBuilder.success(TransactionStatus.COMMITED).build()); + } + + final ListenableFuture> rpcResult = rpc.invokeRpc( + NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT); + + final ListenableFuture> transformed = Futures.transform(rpcResult, + new Function, RpcResult>() { + @Override + public RpcResult apply(final RpcResult input) { + if (input.isSuccessful()) { + return RpcResultBuilder.success(TransactionStatus.COMMITED).build(); + } else { + final RpcResultBuilder failed = RpcResultBuilder.failed(); + for (final RpcError rpcError : input.getErrors()) { + failed.withError(rpcError.getErrorType(), rpcError.getTag(), rpcError.getMessage(), + rpcError.getApplicationTag(), rpcError.getInfo(), rpcError.getCause()); + } + return failed.build(); + } } - return failed.build(); - } - } - }); + }); - // FIXME 732 detect commit failure + Futures.addCallback(transformed, this); + return transformed; + } + + @Override + public void onSuccess(final RpcResult result) { + LOG.debug("{}: Write successful, transaction: {}", id, getIdentifier()); + } + + @Override + public void onFailure(final Throwable t) { + LOG.warn("{}: Write failed, transaction {}, discarding changes", id, getIdentifier(), t); + discardChanges(); } private void sendEditRpc(final CompositeNode editStructure, final Optional defaultOperation) throws ExecutionException { @@ -200,6 +240,22 @@ public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction { } } + private void sendDiscardChanges() { + final ListenableFuture> discardFuture = rpc.invokeRpc(NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT); + Futures.addCallback(discardFuture, new FutureCallback>() { + @Override + public void onSuccess(final RpcResult result) { + LOG.debug("{}: Discarding transaction: {}", id, getIdentifier()); + } + + @Override + public void onFailure(final Throwable t) { + LOG.error("{}: Discarding changes failed, transaction: {}. Device configuration might be corrupted", id, getIdentifier(), t); + throw new RuntimeException(id + ": Discarding changes failed, transaction " + getIdentifier(), t); + } + }); + } + private CompositeNode createEditConfigStructure(final YangInstanceIdentifier dataPath, final Optional operation, final Optional lastChildOverride) { Preconditions.checkArgument(Iterables.isEmpty(dataPath.getPathArguments()) == false, "Instance identifier with empty path %s", dataPath); @@ -298,13 +354,6 @@ public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction { } } - private ImmutableCompositeNode getCommitRequest() { - final CompositeNodeBuilder commitInput = ImmutableCompositeNode.builder(); - commitInput.setQName(NETCONF_COMMIT_QNAME); - return commitInput.toInstance(); - } - - @Override public Object getIdentifier() { return this; diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java index a6924d9d37..d3faddd471 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java @@ -35,6 +35,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.impl.CompositeNodeTOImpl; import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.data.impl.NodeFactory; import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl; import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; @@ -45,8 +46,7 @@ import org.w3c.dom.Element; public class NetconfMessageTransformUtil { - private NetconfMessageTransformUtil() { - } + private NetconfMessageTransformUtil() {} public static final QName IETF_NETCONF_MONITORING = QName.create("urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", "2010-10-04", "ietf-netconf-monitoring"); public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"); @@ -66,14 +66,27 @@ public class NetconfMessageTransformUtil { public static QName NETCONF_DEFAULT_OPERATION_QNAME = QName.create(NETCONF_OPERATION_QNAME, "default-operation"); public static QName NETCONF_EDIT_CONFIG_QNAME = QName.create(NETCONF_QNAME, "edit-config"); public static QName NETCONF_GET_CONFIG_QNAME = QName.create(NETCONF_QNAME, "get-config"); + public static QName NETCONF_DISCARD_CHANGES_QNAME = QName.create(NETCONF_QNAME, "discard-changes"); public static QName NETCONF_TYPE_QNAME = QName.create(NETCONF_QNAME, "type"); public static QName NETCONF_FILTER_QNAME = QName.create(NETCONF_QNAME, "filter"); public static QName NETCONF_GET_QNAME = QName.create(NETCONF_QNAME, "get"); public static QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc"); + public static URI NETCONF_ROLLBACK_ON_ERROR_URI = URI .create("urn:ietf:params:netconf:capability:rollback-on-error:1.0"); public static String ROLLBACK_ON_ERROR_OPTION = "rollback-on-error"; + public static URI NETCONF_CANDIDATE_URI = URI + .create("urn:ietf:params:netconf:capability:candidate:1.0"); + + // Discard changes message + public static final CompositeNode DISCARD_CHANGES_RPC_CONTENT = + NodeFactory.createImmutableCompositeNode(NETCONF_DISCARD_CHANGES_QNAME, null, Collections.>emptyList()); + + // Commit changes message + public static final CompositeNode COMMIT_RPC_CONTENT = + NodeFactory.createImmutableCompositeNode(NETCONF_COMMIT_QNAME, null, Collections.>emptyList()); + public static Node toFilterStructure(final YangInstanceIdentifier identifier) { Node previous = null; if (Iterables.isEmpty(identifier.getPathArguments())) { @@ -269,5 +282,4 @@ public class NetconfMessageTransformUtil { return it.toInstance(); } } - } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang index 6bad4798c2..e13398b1df 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang @@ -58,6 +58,14 @@ module odl-sal-netconf-connector-cfg { type string; } + container yang-module-capabilities { + leaf-list capability { + type string; + description "Set a list of capabilities to override capabilities provided in device's hello message. + Can be used for devices that do not report any yang modules in their hello message"; + } + } + container dom-registry { uses config:service-ref { refine type { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java index 391bf9c6a4..001b9a8d3a 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java @@ -8,35 +8,35 @@ package org.opendaylight.controller.sal.connect.netconf.listener; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY; +import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0; + +import com.google.common.base.Strings; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ListenableFuture; import io.netty.channel.ChannelFuture; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; - import java.io.ByteArrayInputStream; import java.util.Collection; import java.util.Collections; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; - -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.same; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY; -import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0; - import org.apache.commons.lang3.StringUtils; import org.junit.Before; import org.junit.Test; @@ -56,10 +56,6 @@ import org.opendaylight.yangtools.yang.common.RpcResult; import org.w3c.dom.Document; import org.w3c.dom.Element; -import com.google.common.base.Strings; -import com.google.common.collect.Sets; -import com.google.common.util.concurrent.ListenableFuture; - public class NetconfDeviceCommunicatorTest { @Mock @@ -129,9 +125,9 @@ public class NetconfDeviceCommunicatorTest { verify( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) ); NetconfSessionCapabilities actualCapabilites = netconfSessionCapabilities.getValue(); - assertEquals( "containsCapability", true, actualCapabilites.containsCapability( - NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString() ) ); - assertEquals( "containsCapability", true, actualCapabilites.containsCapability( testCapability ) ); + assertEquals( "containsModuleCapability", true, actualCapabilites.containsNonModuleCapability( + NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()) ); + assertEquals( "containsModuleCapability", false, actualCapabilites.containsNonModuleCapability(testCapability) ); assertEquals( "getModuleBasedCaps", Sets.newHashSet( QName.create( "urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module" )), actualCapabilites.getModuleBasedCaps() ); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java new file mode 100644 index 0000000000..87947b57fa --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java @@ -0,0 +1,50 @@ +package org.opendaylight.controller.sal.connect.netconf.listener; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import com.google.common.collect.Lists; +import java.util.List; +import org.junit.Test; +import org.junit.matchers.JUnitMatchers; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.yangtools.yang.common.QName; + +public class NetconfSessionCapabilitiesTest { + + @Test + public void testMerge() throws Exception { + final List caps1 = Lists.newArrayList( + "namespace:1?module=module1&revision=2012-12-12", + "namespace:2?module=module2&revision=2012-12-12", + "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04", + "urn:ietf:params:netconf:base:1.0", + "urn:ietf:params:netconf:capability:rollback-on-error:1.0" + ); + final NetconfSessionCapabilities sessionCaps1 = NetconfSessionCapabilities.fromStrings(caps1); + assertCaps(sessionCaps1, 2, 3); + + final List caps2 = Lists.newArrayList( + "namespace:3?module=module3&revision=2012-12-12", + "namespace:4?module=module4&revision=2012-12-12", + "randomNonModuleCap" + ); + final NetconfSessionCapabilities sessionCaps2 = NetconfSessionCapabilities.fromStrings(caps2); + assertCaps(sessionCaps2, 1, 2); + + final NetconfSessionCapabilities merged = sessionCaps1.replaceModuleCaps(sessionCaps2); + assertCaps(merged, 2, 2 + 1 /*Preserved monitoring*/); + for (final QName qName : sessionCaps2.getModuleBasedCaps()) { + assertThat(merged.getModuleBasedCaps(), JUnitMatchers.hasItem(qName)); + } + assertThat(merged.getModuleBasedCaps(), JUnitMatchers.hasItem(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING)); + + assertThat(merged.getNonModuleCaps(), JUnitMatchers.hasItem("urn:ietf:params:netconf:base:1.0")); + assertThat(merged.getNonModuleCaps(), JUnitMatchers.hasItem("urn:ietf:params:netconf:capability:rollback-on-error:1.0")); + } + + private void assertCaps(final NetconfSessionCapabilities sessionCaps1, final int nonModuleCaps, final int moduleCaps) { + assertEquals(nonModuleCaps, sessionCaps1.getNonModuleCaps().size()); + assertEquals(moduleCaps, sessionCaps1.getModuleBasedCaps().size()); + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java new file mode 100644 index 0000000000..a65e426d59 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java @@ -0,0 +1,79 @@ +package org.opendaylight.controller.sal.connect.netconf.sal.tx; + +import static junit.framework.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.DISCARD_CHANGES_RPC_CONTENT; + +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; +import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; + +public class NetconfDeviceWriteOnlyTxTest { + + private final RemoteDeviceId id = new RemoteDeviceId("test-mount"); + + @Mock + private RpcImplementation rpc; + @Mock + private DataNormalizer normalizer; + private YangInstanceIdentifier yangIId; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + doReturn(Futures.>immediateFailedFuture(new IllegalStateException("Failed tx"))) + .doReturn(Futures.immediateFuture(RpcResultBuilder.success().build())) + .when(rpc).invokeRpc(any(QName.class), any(CompositeNode.class)); + + yangIId = YangInstanceIdentifier.builder().node(QName.create("namespace", "2012-12-12", "name")).build(); + doReturn(yangIId).when(normalizer).toLegacy(yangIId); + } + + @Test + public void testDiscardCahnges() { + final NetconfDeviceWriteOnlyTx tx = new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, true, true); + final CheckedFuture submitFuture = tx.submit(); + try { + submitFuture.checkedGet(); + } catch (final TransactionCommitFailedException e) { + // verify discard changes was sent + verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT); + return; + } + + fail("Submit should fail"); + } + + + @Test + public void testDiscardCahngesNotSentWithoutCandidate() { + doReturn(Futures.immediateFuture(RpcResultBuilder.success().build())) + .doReturn(Futures.>immediateFailedFuture(new IllegalStateException("Failed tx"))) + .when(rpc).invokeRpc(any(QName.class), any(CompositeNode.class)); + + final NetconfDeviceWriteOnlyTx tx = new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, false, true); + tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId); + verify(rpc).invokeRpc(eq(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), any(CompositeNode.class)); + verifyNoMoreInteractions(rpc); + } + +} diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/ActorConstants.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/ActorConstants.java new file mode 100644 index 0000000000..1f1a0f5cc6 --- /dev/null +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/ActorConstants.java @@ -0,0 +1,19 @@ +/* + * 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.remote.rpc; + + +public class ActorConstants { + public static final String RPC_BROKER = "rpc-broker"; + public static final String RPC_REGISTRY = "rpc-registry"; + public static final String RPC_MANAGER = "rpc"; + + public static final String RPC_BROKER_PATH= "/user/rpc/rpc-broker"; + public static final String RPC_REGISTRY_PATH = "/user/rpc/rpc-registry"; +} diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/RemoteRpcProvider.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/RemoteRpcProvider.java index 3df572d7c2..ac50b8fe5b 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/RemoteRpcProvider.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/RemoteRpcProvider.java @@ -68,7 +68,7 @@ public class RemoteRpcProvider implements AutoCloseable, Provider, SchemaContext SchemaService schemaService = brokerSession.getService(SchemaService.class); schemaContext = schemaService.getGlobalContext(); - rpcManager = actorSystem.actorOf(RpcManager.props(clusterWrapper, schemaContext, brokerSession, rpcProvisionRegistry), "rpc"); + rpcManager = actorSystem.actorOf(RpcManager.props(clusterWrapper, schemaContext, brokerSession, rpcProvisionRegistry), ActorConstants.RPC_MANAGER); LOG.debug("Rpc actors are created."); } diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/RpcManager.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/RpcManager.java index 4925a17c13..5c56455bd0 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/RpcManager.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/RpcManager.java @@ -72,14 +72,14 @@ public class RpcManager extends AbstractUntypedActor { private void createRpcActors() { LOG.debug("Create rpc registry and broker actors"); - rpcRegistry = getContext().actorOf(RpcRegistry.props(clusterWrapper), "rpc-registry"); - rpcBroker = getContext().actorOf(RpcBroker.props(brokerSession, rpcRegistry, schemaContext), "rpc-broker"); + rpcRegistry = getContext().actorOf(RpcRegistry.props(clusterWrapper), ActorConstants.RPC_REGISTRY); + rpcBroker = getContext().actorOf(RpcBroker.props(brokerSession, rpcRegistry, schemaContext), ActorConstants.RPC_BROKER); } private void startListeners() { LOG.debug("Registers rpc listeners"); - String rpcBrokerPath = clusterWrapper.getAddress().toString() + "/user/rpc/rpc-broker"; + String rpcBrokerPath = clusterWrapper.getAddress().toString() + ActorConstants.RPC_BROKER_PATH; rpcListener = new RpcListener(rpcRegistry, rpcBrokerPath); routeChangeListener = new RoutedRpcListener(rpcRegistry, rpcBrokerPath); rpcImplementation = new RemoteRpcImplementation(rpcBroker, schemaContext); diff --git a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RpcRegistry.java b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RpcRegistry.java index 7cb505aa98..e36060cc13 100644 --- a/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RpcRegistry.java +++ b/opendaylight/md-sal/sal-remoterpc-connector/src/main/java/org/opendaylight/controller/remote/rpc/registry/RpcRegistry.java @@ -14,6 +14,7 @@ import akka.cluster.ClusterEvent; import akka.cluster.Member; import akka.japi.Creator; import org.opendaylight.controller.remote.rpc.AbstractUntypedActor; +import org.opendaylight.controller.remote.rpc.ActorConstants; import org.opendaylight.controller.remote.rpc.messages.AddRoutedRpc; import org.opendaylight.controller.remote.rpc.messages.AddRpc; import org.opendaylight.controller.remote.rpc.messages.GetRoutedRpc; @@ -171,7 +172,7 @@ public class RpcRegistry extends AbstractUntypedActor { } if(i == index) { if(!currentNodeAddress.equals(member.address())) { - actor = this.context().actorSelection(member.address() + "/user/rpc-registry"); + actor = this.context().actorSelection(member.address() + ActorConstants.RPC_REGISTRY_PATH); break; } else if(index < memberSize-1){ // pick the next element in the set index++; @@ -180,7 +181,7 @@ public class RpcRegistry extends AbstractUntypedActor { i++; } if(actor == null && previousMember != null) { - actor = this.context().actorSelection(previousMember.address() + "/user/rpc-registry"); + actor = this.context().actorSelection(previousMember.address() + ActorConstants.RPC_REGISTRY_PATH); } } return actor; diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfProviderImpl.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfProviderImpl.java index adb176a65d..559be5aa6f 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfProviderImpl.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfProviderImpl.java @@ -21,13 +21,13 @@ import org.opendaylight.controller.sal.restconf.impl.ControllerContext; import org.opendaylight.controller.sal.streams.websockets.WebSocketServer; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.PortNumber; import org.opendaylight.yangtools.concepts.ListenerRegistration; -import org.opendaylight.yangtools.yang.model.api.SchemaServiceListener; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; public class RestconfProviderImpl implements Provider, AutoCloseable, RestConnector { public final static String NOT_INITALIZED_MSG = "Restconf is not initialized yet. Please try again later"; - private ListenerRegistration listenerRegistration; + private ListenerRegistration listenerRegistration; private PortNumber port; public void setWebsocketPort(PortNumber port) { this.port = port; @@ -43,7 +43,7 @@ public class RestconfProviderImpl implements Provider, AutoCloseable, RestConnec BrokerFacade.getInstance().setDataService(dataService); SchemaService schemaService = session.getService(SchemaService.class); - listenerRegistration = schemaService.registerSchemaServiceListener(ControllerContext.getInstance()); + listenerRegistration = schemaService.registerSchemaContextListener(ControllerContext.getInstance()); ControllerContext.getInstance().setSchemas(schemaService.getGlobalContext()); ControllerContext.getInstance().setMountService(session.getService(MountService.class)); diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.java index dad7a2cda2..f8bcbe3c61 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.java @@ -508,9 +508,9 @@ public class ControllerContext implements SchemaContextListener { DataSchemaNode ret = container.getDataChildByName(name); if (ret == null) { for (final DataSchemaNode node : container.getChildNodes()) { - if ((node instanceof ChoiceCaseNode)) { - final ChoiceCaseNode caseNode = ((ChoiceCaseNode) node); - DataSchemaNode childByQName = ControllerContext.childByQName(caseNode, name); + if ((node instanceof ChoiceNode)) { + final ChoiceNode choiceNode = ((ChoiceNode) node); + DataSchemaNode childByQName = ControllerContext.childByQName(choiceNode, name); if (childByQName != null) { return childByQName; } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/streams/listeners/Notificator.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/streams/listeners/Notificator.java index cf1bcd6a30..99bd8c5aaf 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/streams/listeners/Notificator.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/streams/listeners/Notificator.java @@ -114,7 +114,7 @@ public class Notificator { result = result.substring(1); } if (result.endsWith("/")) { - result = result.substring(0, result.length()); + result = result.substring(0, result.length()-1); } return result; } diff --git a/opendaylight/md-sal/samples/pom.xml b/opendaylight/md-sal/samples/pom.xml index c601647a2e..ae7d323480 100644 --- a/opendaylight/md-sal/samples/pom.xml +++ b/opendaylight/md-sal/samples/pom.xml @@ -15,6 +15,7 @@ toaster toaster-consumer toaster-provider + toaster-config l2switch diff --git a/opendaylight/md-sal/samples/toaster-config/pom.xml b/opendaylight/md-sal/samples/toaster-config/pom.xml new file mode 100644 index 0000000000..b30c4ba12f --- /dev/null +++ b/opendaylight/md-sal/samples/toaster-config/pom.xml @@ -0,0 +1,46 @@ + + + + + 4.0.0 + + org.opendaylight.controller.samples + sal-samples + 1.1-SNAPSHOT + + toaster-config + Configuration files for toaster + jar + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-artifacts + + attach-artifact + + package + + + + ${project.build.directory}/classes/initial/03-toaster-sample.xml + xml + config + + + + + + + + + diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/03-toaster-sample.xml b/opendaylight/md-sal/samples/toaster-config/src/main/resources/initial/03-toaster-sample.xml similarity index 99% rename from opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/03-toaster-sample.xml rename to opendaylight/md-sal/samples/toaster-config/src/main/resources/initial/03-toaster-sample.xml index 3958e18560..2e8c7d5ce6 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/03-toaster-sample.xml +++ b/opendaylight/md-sal/samples/toaster-config/src/main/resources/initial/03-toaster-sample.xml @@ -26,7 +26,7 @@ binding:binding-async-data-broker binding-data-broker - + binding:binding-notification-service @@ -54,7 +54,7 @@ - + diff --git a/opendaylight/netconf/netconf-config/pom.xml b/opendaylight/netconf/netconf-config/pom.xml new file mode 100644 index 0000000000..db5d14d75a --- /dev/null +++ b/opendaylight/netconf/netconf-config/pom.xml @@ -0,0 +1,46 @@ + + + + + 4.0.0 + + org.opendaylight.controller + netconf-subsystem + 0.2.5-SNAPSHOT + + netconf-config + Configuration files for netconf + jar + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-artifacts + + attach-artifact + + package + + + + ${project.build.directory}/classes/initial/01-netconf.xml + xml + config + + + + + + + + + diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/01-netconf.xml b/opendaylight/netconf/netconf-config/src/main/resources/initial/01-netconf.xml similarity index 100% rename from opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/01-netconf.xml rename to opendaylight/netconf/netconf-config/src/main/resources/initial/01-netconf.xml diff --git a/opendaylight/netconf/netconf-connector-config/pom.xml b/opendaylight/netconf/netconf-connector-config/pom.xml new file mode 100644 index 0000000000..d9cc5eab43 --- /dev/null +++ b/opendaylight/netconf/netconf-connector-config/pom.xml @@ -0,0 +1,46 @@ + + + + + 4.0.0 + + org.opendaylight.controller + netconf-subsystem + 0.2.5-SNAPSHOT + + netconf-connector-config + Configuration files for netconf-connector + jar + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-artifacts + + attach-artifact + + package + + + + ${project.build.directory}/classes/initial/99-netconf-connector.xml + xml + config + + + + + + + + + diff --git a/opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/99-netconf-connector.xml b/opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml similarity index 100% rename from opendaylight/distribution/opendaylight/src/main/resources/configuration/initial/99-netconf-connector.xml rename to opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml diff --git a/opendaylight/netconf/pom.xml b/opendaylight/netconf/pom.xml index 937949a17e..c72705d50e 100644 --- a/opendaylight/netconf/pom.xml +++ b/opendaylight/netconf/pom.xml @@ -20,6 +20,7 @@ netconf-api netconf-cli + netconf-config netconf-impl config-netconf-connector netconf-util @@ -32,6 +33,7 @@ netconf-monitoring ietf-netconf-monitoring ietf-netconf-monitoring-extension + netconf-connector-config diff --git a/opendaylight/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/NeutronSubnet.java b/opendaylight/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/NeutronSubnet.java index 840029006b..1f10b39513 100644 --- a/opendaylight/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/NeutronSubnet.java +++ b/opendaylight/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/NeutronSubnet.java @@ -75,7 +75,7 @@ public class NeutronSubnet extends ConfigurationObject implements Serializable, */ List myPorts; - boolean gatewayIPAssigned; + Boolean gatewayIPAssigned; public NeutronSubnet() { myPorts = new ArrayList(); @@ -299,7 +299,7 @@ public class NeutronSubnet extends ConfigurationObject implements Serializable, try { SubnetUtils util = new SubnetUtils(cidr); SubnetInfo info = util.getInfo(); - if (gatewayIP == null) { + if (gatewayIP == null || ("").equals(gatewayIP)) { gatewayIP = info.getLowAddress(); } if (allocationPools.size() < 1) { @@ -460,6 +460,10 @@ public class NeutronSubnet extends ConfigurationObject implements Serializable, gatewayIPAssigned = false; } + public Boolean getGatewayIPAllocated() { + return gatewayIPAssigned; + } + @Override public String toString() { return "NeutronSubnet [subnetUUID=" + subnetUUID + ", networkUUID=" + networkUUID + ", name=" + name diff --git a/opendaylight/northbound/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronRoutersNorthbound.java b/opendaylight/northbound/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronRoutersNorthbound.java index 17b2fcfcf9..806e853b36 100644 --- a/opendaylight/northbound/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronRoutersNorthbound.java +++ b/opendaylight/northbound/networkconfiguration/neutron/src/main/java/org/opendaylight/controller/networkconfig/neutron/northbound/NeutronRoutersNorthbound.java @@ -422,7 +422,9 @@ public class NeutronRoutersNorthbound { if (instances != null) { for (Object instance : instances) { INeutronRouterAware service = (INeutronRouterAware) instance; - service.canAttachInterface(target, input); + int status = service.canAttachInterface(target, input); + if (status < 200 || status > 299) + return Response.status(status).build(); } } @@ -498,7 +500,9 @@ public class NeutronRoutersNorthbound { if (instances != null) { for (Object instance : instances) { INeutronRouterAware service = (INeutronRouterAware) instance; - service.canDetachInterface(target, input); + int status = service.canDetachInterface(target, input); + if (status < 200 || status > 299) + return Response.status(status).build(); } }