From d61c1618dd731504a6271c2896c709d746023e86 Mon Sep 17 00:00:00 2001 From: Maros Marsalek Date: Thu, 5 Feb 2015 15:49:44 +0100 Subject: [PATCH] BUG-2632 Netconf connector (optionally) reconnects after a schema change is detected A notification listener is used for schema change detection Change-Id: Ieab4eaf9e87b2b88dd2dc5429039494c8f9b5731 Signed-off-by: Maros Marsalek --- .../md-sal/sal-netconf-connector/pom.xml | 4 ++ .../netconf/NetconfConnectorModule.java | 22 +++--- .../sal/connect/api/RemoteDevice.java | 4 +- .../sal/connect/netconf/NetconfDevice.java | 67 +++++++++++++++++-- .../connect/netconf/NotificationHandler.java | 18 ++++- .../listener/NetconfDeviceCommunicator.java | 54 +++++++-------- .../listener/NetconfSessionPreferences.java | 5 ++ .../util/NetconfMessageTransformUtil.java | 12 ++++ .../yang/odl-sal-netconf-connector-cfg.yang | 7 ++ .../connect/netconf/NetconfDeviceTest.java | 22 +++--- .../NetconfDeviceCommunicatorTest.java | 32 +++++---- .../initial/99-netconf-connector.xml | 1 + .../netconf/it/NetconfITSecureTest.java | 5 +- 13 files changed, 173 insertions(+), 80 deletions(-) diff --git a/opendaylight/md-sal/sal-netconf-connector/pom.xml b/opendaylight/md-sal/sal-netconf-connector/pom.xml index add889fa3e..61c83a68de 100644 --- a/opendaylight/md-sal/sal-netconf-connector/pom.xml +++ b/opendaylight/md-sal/sal-netconf-connector/pom.xml @@ -33,6 +33,10 @@ org.opendaylight.controller ietf-netconf-monitoring + + org.opendaylight.controller + ietf-netconf-notifications + org.opendaylight.controller netconf-client 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 460e072d9a..b966fae3d4 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java @@ -9,6 +9,7 @@ package org.opendaylight.controller.config.yang.md.sal.connector.netconf; import static org.opendaylight.controller.config.api.JmxAttributeValidationException.checkCondition; import static org.opendaylight.controller.config.api.JmxAttributeValidationException.checkNotNull; + import com.google.common.base.Optional; import io.netty.util.concurrent.EventExecutor; import java.math.BigDecimal; @@ -87,7 +88,6 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co } userCapabilities = getUserCapabilities(); - } private boolean isHostAddressPresent(final Host address) { @@ -111,17 +111,17 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co new NetconfDevice.SchemaResourcesDTO(schemaRegistry, schemaContextFactory, new NetconfStateSchemas.NetconfStateSchemasResolverImpl()); final NetconfDevice device = - new NetconfDevice(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, new NetconfMessageTransformer()); + new NetconfDevice(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, new NetconfMessageTransformer(), true); final NetconfDeviceCommunicator listener = userCapabilities.isPresent() ? new NetconfDeviceCommunicator(id, device, userCapabilities.get()) : new NetconfDeviceCommunicator(id, device); final NetconfReconnectingClientConfiguration clientConfig = getClientConfig(listener); - final NetconfClientDispatcher dispatcher = getClientDispatcherDependency(); + listener.initializeRemoteConnection(dispatcher, clientConfig); - return new MyAutoCloseable(listener, salFacade); + return new SalConnectorCloseable(listener, salFacade); } private Optional getUserCapabilities() { @@ -152,7 +152,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co final InetSocketAddress socketAddress = getSocketAddress(); final long clientConnectionTimeoutMillis = getConnectionTimeoutMillis(); - final ReconnectStrategyFactory sf = new MyReconnectStrategyFactory( + final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory( getEventExecutorDependency(), getMaxConnectionAttempts(), getBetweenAttemptsTimeoutMillis(), getSleepFactor()); final ReconnectStrategy strategy = sf.createReconnectStrategy(); @@ -160,21 +160,21 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co .withAddress(socketAddress) .withConnectionTimeoutMillis(clientConnectionTimeoutMillis) .withReconnectStrategy(strategy) - .withSessionListener(listener) .withAuthHandler(new LoginPassword(getUsername(),getPassword())) .withProtocol(getTcpOnly() ? NetconfClientConfiguration.NetconfClientProtocol.TCP : NetconfClientConfiguration.NetconfClientProtocol.SSH) .withConnectStrategyFactory(sf) + .withSessionListener(listener) .build(); } - private static final class MyAutoCloseable implements AutoCloseable { + private static final class SalConnectorCloseable implements AutoCloseable { private final RemoteDeviceHandler salFacade; private final NetconfDeviceCommunicator listener; - public MyAutoCloseable(final NetconfDeviceCommunicator listener, - final RemoteDeviceHandler salFacade) { + public SalConnectorCloseable(final NetconfDeviceCommunicator listener, + final RemoteDeviceHandler salFacade) { this.listener = listener; this.salFacade = salFacade; } @@ -186,13 +186,13 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co } } - private static final class MyReconnectStrategyFactory implements ReconnectStrategyFactory { + private static final class TimedReconnectStrategyFactory implements ReconnectStrategyFactory { private final Long connectionAttempts; private final EventExecutor executor; private final double sleepFactor; private final int minSleep; - MyReconnectStrategyFactory(final EventExecutor executor, final Long maxConnectionAttempts, final int minSleep, final BigDecimal sleepFactor) { + TimedReconnectStrategyFactory(final EventExecutor executor, final Long maxConnectionAttempts, final int minSleep, final BigDecimal sleepFactor) { if (maxConnectionAttempts != null && maxConnectionAttempts > 0) { connectionAttempts = maxConnectionAttempts; } else { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java index 9423dbf1d2..ca12e596e6 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/api/RemoteDevice.java @@ -10,9 +10,9 @@ package org.opendaylight.controller.sal.connect.api; /** * */ -public interface RemoteDevice { +public interface RemoteDevice> { - void onRemoteSessionUp(PREF remoteSessionCapabilities, RemoteDeviceCommunicator listener); + void onRemoteSessionUp(PREF remoteSessionCapabilities, LISTENER listener); void onRemoteSessionDown(); 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 39340fa166..9a5b239024 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java @@ -32,12 +32,17 @@ import org.opendaylight.controller.sal.connect.api.RemoteDevice; import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCapabilities; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceRpc; import org.opendaylight.controller.sal.connect.netconf.schema.NetconfRemoteSchemaYangSourceProvider; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.unavailable.capabilities.UnavailableCapability.FailureReason; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.fields.unavailable.capabilities.UnavailableCapability; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException; import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory; @@ -54,7 +59,7 @@ import org.slf4j.LoggerFactory; /** * This is a mediator between NetconfDeviceCommunicator and NetconfDeviceSalFacade */ -public final class NetconfDevice implements RemoteDevice { +public final class NetconfDevice implements RemoteDevice { private static final Logger logger = LoggerFactory.getLogger(NetconfDevice.class); @@ -66,6 +71,7 @@ public final class NetconfDevice implements RemoteDevice salFacade; @@ -78,7 +84,14 @@ public final class NetconfDevice implements RemoteDevice salFacade, final ExecutorService globalProcessingExecutor, final MessageTransformer messageTransformer) { + this(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, messageTransformer, false); + } + + // FIXME reduce parameters + public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler salFacade, + final ExecutorService globalProcessingExecutor, final MessageTransformer messageTransformer, final boolean reconnectOnSchemasChange) { this.id = id; + this.reconnectOnSchemasChange = reconnectOnSchemasChange; this.schemaRegistry = schemaResourcesDTO.getSchemaRegistry(); this.messageTransformer = messageTransformer; this.schemaContextFactory = schemaResourcesDTO.getSchemaContextFactory(); @@ -90,7 +103,7 @@ public final class NetconfDevice implements RemoteDevice listener) { + final NetconfDeviceCommunicator listener) { // SchemaContext setup has to be performed in a dedicated thread since // we are in a netty thread in this method // Yang models are being downloaded in this method and it would cause a @@ -103,6 +116,10 @@ public final class NetconfDevice implements RemoteDevice sourceResolverFuture = processingExecutor.submit(task); + if(shouldListenOnSchemaChange(remoteSessionCapabilities)) { + registerToBaseNetconfStream(deviceRpc, listener); + } + final FutureCallback resolvedSourceCallback = new FutureCallback() { @Override public void onSuccess(final DeviceSources result) { @@ -125,12 +142,49 @@ public final class NetconfDevice implements RemoteDevice> rpcResultListenableFuture = + deviceRpc.invokeRpc(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME, NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_CONTENT); + + final NotificationHandler.NotificationFilter filter = new NotificationHandler.NotificationFilter() { + @Override + public Optional filterNotification(final CompositeNode notification) { + if (isCapabilityChanged(notification)) { + logger.info("{}: Schemas change detected, reconnecting", id); + // Only disconnect is enough, the reconnecting nature of the connector will take care of reconnecting + listener.disconnect(); + return Optional.absent(); + } + return Optional.of(notification); + } + + private boolean isCapabilityChanged(final CompositeNode notification) { + return notification.getNodeType().equals(NetconfCapabilityChange.QNAME); + } + }; + + Futures.addCallback(rpcResultListenableFuture, new FutureCallback>() { + @Override + public void onSuccess(final RpcResult result) { + notificationHandler.addNotificationFilter(filter); + } + + @Override + public void onFailure(final Throwable t) { + logger.warn("Unable to subscribe to base notification stream. Schemas will not be reloaded on the fly", t); + } + }); + } + + private boolean shouldListenOnSchemaChange(final NetconfSessionPreferences remoteSessionCapabilities) { + return remoteSessionCapabilities.isNotificationsSupported() && reconnectOnSchemasChange; + } + private void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionPreferences remoteSessionCapabilities, final NetconfDeviceRpc deviceRpc) { updateMessageTransformer(result); salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc); notificationHandler.onRemoteSchemaUp(); - logger.debug("{}: Initialization in sal successful", id); logger.info("{}: Netconf connector initialized successfully", id); } @@ -150,7 +204,6 @@ public final class NetconfDevice implements RemoteDevice unresolvedSources = resolutionException.getUnsatisfiedImports().keySet(); - capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(unresolvedSources), FailureReason.UnableToResolve); + capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(unresolvedSources), UnavailableCapability.FailureReason.UnableToResolve); logger.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only", id, resolutionException.getUnsatisfiedImports()); setUpSchema(resolutionException.getResolvedSources()); // unknown error, fail diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NotificationHandler.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NotificationHandler.java index cc8960fb4f..b5927f0bd5 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NotificationHandler.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NotificationHandler.java @@ -7,6 +7,7 @@ */ package org.opendaylight.controller.sal.connect.netconf; +import com.google.common.base.Optional; import com.google.common.base.Preconditions; import java.util.LinkedList; import java.util.List; @@ -31,6 +32,7 @@ final class NotificationHandler { private final MessageTransformer messageTransformer; private final RemoteDeviceId id; private boolean passNotifications = false; + private NotificationFilter filter; NotificationHandler(final RemoteDeviceHandler salFacade, final MessageTransformer messageTransformer, final RemoteDeviceId id) { this.salFacade = Preconditions.checkNotNull(salFacade); @@ -70,9 +72,21 @@ final class NotificationHandler { queue.add(notification); } - private void passNotification(final CompositeNode parsedNotification) { + private synchronized void passNotification(final CompositeNode parsedNotification) { logger.debug("{}: Forwarding notification {}", id, parsedNotification); Preconditions.checkNotNull(parsedNotification); - salFacade.onNotification(parsedNotification); + + if(filter == null || filter.filterNotification(parsedNotification).isPresent()) { + salFacade.onNotification(parsedNotification); + } + } + + synchronized void addNotificationFilter(final NotificationFilter filter) { + this.filter = filter; + } + + static interface NotificationFilter { + + Optional filterNotification(CompositeNode notification); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java index 556fc2f1d2..8553820b40 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicator.java @@ -47,7 +47,7 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceCommunicator.class); - private final RemoteDevice remoteDevice; + private final RemoteDevice remoteDevice; private final Optional overrideNetconfCapabilities; private final RemoteDeviceId id; private final Lock sessionLock = new ReentrantLock(); @@ -57,17 +57,17 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, private NetconfClientSession session; private Future initFuture; - public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, - final NetconfSessionPreferences netconfSessionPreferences) { - this(id, remoteDevice, Optional.of(netconfSessionPreferences)); + public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, + final NetconfSessionPreferences NetconfSessionPreferences) { + this(id, remoteDevice, Optional.of(NetconfSessionPreferences)); } public NetconfDeviceCommunicator(final RemoteDeviceId id, - final RemoteDevice remoteDevice) { + final RemoteDevice remoteDevice) { this(id, remoteDevice, Optional.absent()); } - private NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, + private NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, final Optional overrideNetconfCapabilities) { this.id = id; this.remoteDevice = remoteDevice; @@ -97,14 +97,15 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, } } - public void initializeRemoteConnection(final NetconfClientDispatcher dispatch, - final NetconfClientConfiguration config) { + public void initializeRemoteConnection(final NetconfClientDispatcher dispatcher, final NetconfClientConfiguration config) { + // TODO 2313 extract listener from configuration if(config instanceof NetconfReconnectingClientConfiguration) { - initFuture = dispatch.createReconnectingClient((NetconfReconnectingClientConfiguration) config); + initFuture = dispatcher.createReconnectingClient((NetconfReconnectingClientConfiguration) config); } else { - initFuture = dispatch.createClient(config); + initFuture = dispatcher.createClient(config); } + initFuture.addListener(new GenericFutureListener>(){ @Override @@ -115,6 +116,13 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, } } }); + + } + + public void disconnect() { + if(session != null) { + session.close(); + } } private void tearDown( String reason ) { @@ -158,18 +166,14 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, } } - private RpcResult createSessionDownRpcResult() - { + private RpcResult createSessionDownRpcResult() { return createErrorRpcResult( RpcError.ErrorType.TRANSPORT, String.format( "The netconf session to %1$s is disconnected", id.getName() ) ); } - private RpcResult createErrorRpcResult( RpcError.ErrorType errorType, String message ) - { + private RpcResult createErrorRpcResult( RpcError.ErrorType errorType, String message ) { return RpcResultBuilder.failed() - .withError( errorType, NetconfDocumentedException.ErrorTag.operation_failed.getTagValue(), - message ) - .build(); + .withError(errorType, NetconfDocumentedException.ErrorTag.operation_failed.getTagValue(), message).build(); } @Override @@ -194,6 +198,7 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, if(session != null) { session.close(); } + tearDown(id + ": Netconf session closed"); } @@ -232,14 +237,12 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, logger.debug("{}: Message received {}", id, message); if(logger.isTraceEnabled()) { - logger.trace( "{}: Matched request: {} to response: {}", id, - msgToS( request.request ), msgToS( message ) ); + logger.trace( "{}: Matched request: {} to response: {}", id, msgToS( request.request ), msgToS( message ) ); } try { NetconfMessageTransformUtil.checkValidReply( request.request, message ); - } - catch (final NetconfDocumentedException e) { + } catch (final NetconfDocumentedException e) { logger.warn( "{}: Invalid request-reply match, reply message contains different message-id, request: {}, response: {}", id, msgToS( request.request ), msgToS( message ), e ); @@ -250,8 +253,7 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, try { NetconfMessageTransformUtil.checkSuccessReply(message); - } - catch(final NetconfDocumentedException e) { + } catch(final NetconfDocumentedException e) { logger.warn( "{}: Error reply from remote device, request: {}, response: {}", id, msgToS( request.request ), msgToS( message ), e ); @@ -269,13 +271,11 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, } @Override - public ListenableFuture> sendRequest( - final NetconfMessage message, final QName rpc) { + public ListenableFuture> sendRequest(final NetconfMessage message, final QName rpc) { sessionLock.lock(); try { return sendRequestWithLock( message, rpc ); - } - finally { + } finally { sessionLock.unlock(); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferences.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferences.java index 572885bcef..89211ede77 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferences.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionPreferences.java @@ -105,6 +105,11 @@ public final class NetconfSessionPreferences { return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_RUNNING_WRITABLE_URI.toString()); } + public boolean isNotificationsSupported() { + return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_NOTIFICATONS_URI.toString()) + || containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_NOTIFICATIONS); + } + public boolean isMonitoringSupported() { return containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING) || containsNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java index 9eba24179f..5e3ad2c1fb 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java @@ -26,7 +26,9 @@ import javax.annotation.Nullable; import org.opendaylight.controller.netconf.api.NetconfDocumentedException; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscriptionInput; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.RpcError; import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity; @@ -51,6 +53,7 @@ import org.w3c.dom.Element; public class NetconfMessageTransformUtil { public static final String MESSAGE_ID_ATTR = "message-id"; + public static final QName CREATE_SUBSCRIPTION_RPC_QNAME = QName.cachedReference(QName.create(CreateSubscriptionInput.QNAME, "create-subscription")); private NetconfMessageTransformUtil() {} @@ -61,6 +64,8 @@ public class NetconfMessageTransformUtil { public static final QName IETF_NETCONF_MONITORING_SCHEMA_VERSION = QName.create(IETF_NETCONF_MONITORING, "version"); public static final QName IETF_NETCONF_MONITORING_SCHEMA_NAMESPACE = QName.create(IETF_NETCONF_MONITORING, "namespace"); + public static final QName IETF_NETCONF_NOTIFICATIONS = QName.create(NetconfCapabilityChange.QNAME, "ietf-netconf-notifications"); + public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"); public static QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf"); public static QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data"); @@ -91,6 +96,9 @@ public class NetconfMessageTransformUtil { public static URI NETCONF_CANDIDATE_URI = URI .create("urn:ietf:params:netconf:capability:candidate:1.0"); + public static URI NETCONF_NOTIFICATONS_URI = URI + .create("urn:ietf:params:netconf:capability:notification:1.0"); + public static URI NETCONF_RUNNING_WRITABLE_URI = URI .create("urn:ietf:params:netconf:capability:writable-running:1.0"); @@ -105,6 +113,10 @@ public class NetconfMessageTransformUtil { public static final CompositeNode COMMIT_RPC_CONTENT = NodeFactory.createImmutableCompositeNode(NETCONF_COMMIT_QNAME, null, Collections.>emptyList()); + // Create-subscription changes message + public static final CompositeNode CREATE_SUBSCRIPTION_RPC_CONTENT = + NodeFactory.createImmutableCompositeNode(CREATE_SUBSCRIPTION_RPC_QNAME, null, Collections.>emptyList()); + public static Node toFilterStructure(final YangInstanceIdentifier identifier) { Node previous = null; if (Iterables.isEmpty(identifier.getPathArguments())) { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang index e13398b1df..7059a14aa3 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang @@ -66,6 +66,13 @@ module odl-sal-netconf-connector-cfg { } } + leaf reconnect-on-changed-schema { + type boolean; + default false; + description "If true, the connector would auto disconnect/reconnect when schemas are changed in the remote device. + The connector subscribes (right after connect) to base netconf notifications and listens for netconf-capability-change notification"; + } + container dom-registry { uses config:service-ref { refine type { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java index 0ddafa375f..ec945e050b 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/NetconfDeviceTest.java @@ -37,9 +37,9 @@ import org.mockito.stubbing.Answer; import org.opendaylight.controller.netconf.api.NetconfMessage; import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants; import org.opendaylight.controller.sal.connect.api.MessageTransformer; -import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.controller.sal.connect.api.SchemaSourceProviderFactory; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceRpc; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; @@ -101,7 +101,7 @@ public class NetconfDeviceTest { final ArrayList capList = Lists.newArrayList(TEST_CAPABILITY); final RemoteDeviceHandler facade = getFacade(); - final RemoteDeviceCommunicator listener = getListener(); + final NetconfDeviceCommunicator listener = getListener(); final SchemaContextFactory schemaFactory = getSchemaFactory(); @@ -115,7 +115,7 @@ public class NetconfDeviceTest { final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), schemaFactory, stateSchemasResolver); - final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer()); + final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer(), true); // Monitoring not supported final NetconfSessionPreferences sessionCaps = getSessionCaps(false, capList); device.onRemoteSessionUp(sessionCaps, listener); @@ -128,7 +128,7 @@ public class NetconfDeviceTest { @Test public void testNetconfDeviceMissingSource() throws Exception { final RemoteDeviceHandler facade = getFacade(); - final RemoteDeviceCommunicator listener = getListener(); + final NetconfDeviceCommunicator listener = getListener(); final SchemaContextFactory schemaFactory = getSchemaFactory(); @@ -147,7 +147,7 @@ public class NetconfDeviceTest { final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), schemaFactory, stateSchemasResolver); - final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer()); + final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), getMessageTransformer(), true); // Monitoring supported final NetconfSessionPreferences sessionCaps = getSessionCaps(true, Lists.newArrayList(TEST_CAPABILITY, TEST_CAPABILITY2)); device.onRemoteSessionUp(sessionCaps, listener); @@ -167,13 +167,13 @@ public class NetconfDeviceTest { @Test public void testNotificationBeforeSchema() throws Exception { final RemoteDeviceHandler facade = getFacade(); - final RemoteDeviceCommunicator listener = getListener(); + final NetconfDeviceCommunicator listener = getListener(); final MessageTransformer messageTransformer = getMessageTransformer(); final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), getSchemaFactory(), stateSchemasResolver); - final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), messageTransformer); + final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), messageTransformer, true); device.onNotification(netconfMessage); device.onNotification(netconfMessage); @@ -196,14 +196,14 @@ public class NetconfDeviceTest { @Test public void testNetconfDeviceReconnect() throws Exception { final RemoteDeviceHandler facade = getFacade(); - final RemoteDeviceCommunicator listener = getListener(); + final NetconfDeviceCommunicator listener = getListener(); final SchemaContextFactory schemaContextProviderFactory = getSchemaFactory(); final MessageTransformer messageTransformer = getMessageTransformer(); final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(getSchemaRegistry(), schemaContextProviderFactory, stateSchemasResolver); - final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), messageTransformer); + final NetconfDevice device = new NetconfDevice(schemaResourcesDTO, getId(), facade, getExecutor(), messageTransformer, true); final NetconfSessionPreferences sessionCaps = getSessionCaps(true, Lists.newArrayList(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION)); device.onRemoteSessionUp(sessionCaps, listener); @@ -299,8 +299,8 @@ public class NetconfDeviceTest { capabilities); } - public RemoteDeviceCommunicator getListener() throws Exception { - final RemoteDeviceCommunicator remoteDeviceCommunicator = mockCloseableClass(RemoteDeviceCommunicator.class); + public NetconfDeviceCommunicator getListener() throws Exception { + final NetconfDeviceCommunicator remoteDeviceCommunicator = mockCloseableClass(NetconfDeviceCommunicator.class); doReturn(Futures.immediateFuture(rpcResult)).when(remoteDeviceCommunicator).sendRequest(any(NetconfMessage.class), any(QName.class)); return remoteDeviceCommunicator; } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java index fad3d8e1ea..68fe87fb60 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java @@ -56,10 +56,10 @@ import org.opendaylight.controller.netconf.api.NetconfTerminationReason; import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl; import org.opendaylight.controller.netconf.client.NetconfClientSession; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; +import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfiguration; import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder; import org.opendaylight.controller.netconf.nettyutil.handler.ssh.authentication.LoginPassword; import org.opendaylight.controller.sal.connect.api.RemoteDevice; -import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.protocol.framework.ReconnectStrategy; @@ -77,7 +77,7 @@ public class NetconfDeviceCommunicatorTest { NetconfClientSession mockSession; @Mock - RemoteDevice mockDevice; + RemoteDevice mockDevice; NetconfDeviceCommunicator communicator; @@ -85,16 +85,15 @@ public class NetconfDeviceCommunicatorTest { public void setUp() throws Exception { MockitoAnnotations.initMocks( this ); - communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test" ), mockDevice ); + communicator = new NetconfDeviceCommunicator( new RemoteDeviceId( "test" ), mockDevice); } @SuppressWarnings("unchecked") - void setupSession() - { - doReturn( Collections.emptySet() ).when( mockSession ).getServerCapabilities(); - doNothing().when( mockDevice ).onRemoteSessionUp( any( NetconfSessionPreferences.class ), - any( RemoteDeviceCommunicator.class ) ); - communicator.onSessionUp( mockSession ); + void setupSession() { + doReturn(Collections.emptySet()).when(mockSession).getServerCapabilities(); + doNothing().when(mockDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class), + any(NetconfDeviceCommunicator.class)); + communicator.onSessionUp(mockSession); } private ListenableFuture> sendRequest() throws Exception { @@ -130,16 +129,16 @@ public class NetconfDeviceCommunicatorTest { testCapability ); doReturn( serverCapabilities ).when( mockSession ).getServerCapabilities(); - ArgumentCaptor netconfSessionCapabilities = + ArgumentCaptor NetconfSessionPreferences = ArgumentCaptor.forClass( NetconfSessionPreferences.class ); - doNothing().when( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) ); + doNothing().when( mockDevice ).onRemoteSessionUp( NetconfSessionPreferences.capture(), eq( communicator ) ); communicator.onSessionUp( mockSession ); verify( mockSession ).getServerCapabilities(); - verify( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) ); + verify( mockDevice ).onRemoteSessionUp( NetconfSessionPreferences.capture(), eq( communicator ) ); - NetconfSessionPreferences actualCapabilites = netconfSessionCapabilities.getValue(); + NetconfSessionPreferences actualCapabilites = NetconfSessionPreferences.getValue(); assertEquals( "containsModuleCapability", true, actualCapabilites.containsNonModuleCapability( NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()) ); assertEquals( "containsModuleCapability", false, actualCapabilites.containsNonModuleCapability(testCapability) ); @@ -340,7 +339,7 @@ public class NetconfDeviceCommunicatorTest { */ @Test public void testNetconfDeviceReconnectInCommunicator() throws Exception { - final RemoteDevice device = mock(RemoteDevice.class); + final RemoteDevice device = mock(RemoteDevice.class); final TimedReconnectStrategy timedReconnectStrategy = new TimedReconnectStrategy(GlobalEventExecutor.INSTANCE, 10000, 0, 1.0, null, 100L, null); final ReconnectStrategy reconnectStrategy = spy(new ReconnectStrategy() { @@ -360,11 +359,11 @@ public class NetconfDeviceCommunicatorTest { } }); - final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(new RemoteDeviceId("test"), device); final EventLoopGroup group = new NioEventLoopGroup(); final Timer time = new HashedWheelTimer(); try { - final NetconfClientConfiguration cfg = NetconfReconnectingClientConfigurationBuilder.create() + final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(new RemoteDeviceId("test"), device); + final NetconfReconnectingClientConfiguration cfg = NetconfReconnectingClientConfigurationBuilder.create() .withAddress(new InetSocketAddress("localhost", 65000)) .withReconnectStrategy(reconnectStrategy) .withConnectStrategyFactory(new ReconnectStrategyFactory() { @@ -379,7 +378,6 @@ public class NetconfDeviceCommunicatorTest { .withSessionListener(listener) .build(); - listener.initializeRemoteConnection(new NetconfClientDispatcherImpl(group, group, time), cfg); verify(reconnectStrategy, timeout((int) TimeUnit.MINUTES.toMillis(3)).times(101)).scheduleReconnect(any(Throwable.class)); diff --git a/opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml b/opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml index 7155eb8883..2bd919df94 100644 --- a/opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml +++ b/opendaylight/netconf/netconf-connector-config/src/main/resources/initial/99-netconf-connector.xml @@ -20,6 +20,7 @@ admin admin false + true prefix:netty-event-executor global-event-executor diff --git a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java index a938fbf565..029aefff6e 100644 --- a/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java +++ b/opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfITSecureTest.java @@ -58,7 +58,6 @@ import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil; import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil; import org.opendaylight.controller.netconf.util.xml.XmlUtil; import org.opendaylight.controller.sal.connect.api.RemoteDevice; -import org.opendaylight.controller.sal.connect.api.RemoteDeviceCommunicator; import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; @@ -199,8 +198,8 @@ public class NetconfITSecureTest extends AbstractNetconfConfigTest { } static NetconfDeviceCommunicator getSessionListener() { - RemoteDevice mockedRemoteDevice = mock(RemoteDevice.class); - doNothing().when(mockedRemoteDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class), any(RemoteDeviceCommunicator.class)); + RemoteDevice mockedRemoteDevice = mock(RemoteDevice.class); + doNothing().when(mockedRemoteDevice).onRemoteSessionUp(any(NetconfSessionPreferences.class), any(NetconfDeviceCommunicator.class)); doNothing().when(mockedRemoteDevice).onRemoteSessionDown(); return new NetconfDeviceCommunicator(new RemoteDeviceId("secure-test"), mockedRemoteDevice); } -- 2.36.6