Bug-6346: Allow over-ride of non-module capabilities 97/44897/20
authorOm Prakash <op317q@att.com>
Wed, 31 Aug 2016 03:33:43 +0000 (23:33 -0400)
committerJakub Morvay <jmorvay@cisco.com>
Fri, 3 Mar 2017 17:00:10 +0000 (18:00 +0100)
This feature will allow user to override non-module based capability like
:writable-running or :candidate capabilities of the device. It can used
for devices that do not report or incorrectly report non-module based
capabilities in their hello message.

Change-Id: I1ba4ac1ca84a528434658e8af7c3db981146edb6
Signed-off-by: Om Prakash <op317q@att.com>
Signed-off-by: Jakub Morvay <jmorvay@cisco.com>
12 files changed:
netconf/netconf-console/src/test/resources/schemas/netconf-node-topology.yang
netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/RemoteDeviceConnectorImpl.java
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfDeviceCommunicator.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferences.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/UserPreferences.java
netconf/sal-netconf-connector/src/main/yang/netconf-node-topology.yang
netconf/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferencesTest.java
netconf/sal-netconf-connector/src/test/resources/schemas/netconf-node-topology.yang

index 07461d48d75f67327cf0c6e380a4af454fa748ee..d52b93dd25b2e2490cf40ff791036451d9cf2b79 100644 (file)
@@ -66,6 +66,23 @@ module netconf-node-topology {
             }
         }
 
+        container non-module-capabilities {
+            config true;
+            leaf override {
+                type boolean;
+                default false;
+                description "Whether to override or merge this list of non-module based capabilities with non-module
+                             based capabilities from device";
+            }
+
+            leaf-list capability {
+                type string;
+                description "Set a list of non-module based capabilities to override or merge non-module capabilities
+                             provided in device's hello message. Can be used for devices that do not report or
+                             incorrectly report non-module based capabilities in their hello message";
+            }
+        }
+
         leaf reconnect-on-changed-schema {
             config true;
             type boolean;
@@ -153,6 +170,10 @@ module netconf-node-topology {
                     }
                 }
             }
+            leaf netconf-master-node {
+                config false;
+                type string;
+            }
         }
 
         leaf connected-message {
index 0525789d153eac9f575ee8b05423e075345c2d43..aa8853daa43fae8bcbcb8cc702db72e844a8ae09 100644 (file)
@@ -21,6 +21,7 @@ import java.io.File;
 import java.math.BigDecimal;
 import java.net.InetSocketAddress;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -57,6 +58,7 @@ import org.opendaylight.protocol.framework.TimedReconnectStrategy;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
@@ -87,15 +89,6 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
      */
     private static final Map<String, NetconfDevice.SchemaResourcesDTO> schemaResourcesDTOs = new HashMap<>();
 
-    private SchemaSourceRegistry schemaRegistry = NetconfTopologyUtils.DEFAULT_SCHEMA_REPOSITORY;
-    private SchemaRepository schemaRepository = NetconfTopologyUtils.DEFAULT_SCHEMA_REPOSITORY;
-
-    private final NetconfTopologySetup netconfTopologyDeviceSetup;
-    private final RemoteDeviceId remoteDeviceId;
-
-    private SchemaContextFactory schemaContextFactory = NetconfTopologyUtils.DEFAULT_SCHEMA_CONTEXT_FACTORY;
-    private NetconfConnectorDTO deviceCommunicatorDTO;
-
     // Initializes default constant instances for the case when the default schema repository
     // directory cache/schema is used.
     static {
@@ -110,6 +103,13 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
                         NetconfTopologyUtils.DEFAULT_SCHEMA_REPOSITORY));
     }
 
+    private final NetconfTopologySetup netconfTopologyDeviceSetup;
+    private final RemoteDeviceId remoteDeviceId;
+    private SchemaSourceRegistry schemaRegistry = NetconfTopologyUtils.DEFAULT_SCHEMA_REPOSITORY;
+    private final SchemaRepository schemaRepository = NetconfTopologyUtils.DEFAULT_SCHEMA_REPOSITORY;
+    private SchemaContextFactory schemaContextFactory = NetconfTopologyUtils.DEFAULT_SCHEMA_CONTEXT_FACTORY;
+    private NetconfConnectorDTO deviceCommunicatorDTO;
+
     public RemoteDeviceConnectorImpl(final NetconfTopologySetup netconfTopologyDeviceSetup,
                                      final RemoteDeviceId remoteDeviceId) {
 
@@ -136,12 +136,12 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
 
         Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
             @Override
-            public void onSuccess(NetconfDeviceCapabilities result) {
+            public void onSuccess(final NetconfDeviceCapabilities result) {
                 LOG.debug("{}: Connector started successfully", remoteDeviceId);
             }
 
             @Override
-            public void onFailure(@Nullable Throwable throwable) {
+            public void onFailure(@Nullable final Throwable throwable) {
                 LOG.error("{}: Connector failed, {}", remoteDeviceId, throwable);
             }
         });
@@ -152,14 +152,14 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
         Preconditions.checkNotNull(deviceCommunicatorDTO, remoteDeviceId + ": Device communicator was not created.");
         try {
             deviceCommunicatorDTO.close();
-        } catch (Exception e) {
+        } catch (final Exception e) {
             LOG.error("{}: Error at closing device communicator.", remoteDeviceId, e);
         }
     }
 
     @VisibleForTesting
     NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
-                                                         final ActorRef deviceContextActorRef) {
+                                                 final ActorRef deviceContextActorRef) {
         //setup default values since default value is not supported in mdsal
         final Long defaultRequestTimeoutMillis = node.getDefaultRequestTimeoutMillis() == null
                 ? NetconfTopologyUtils.DEFAULT_REQUEST_TIMEOUT_MILLIS : node.getDefaultRequestTimeoutMillis();
@@ -168,7 +168,7 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
         final Boolean reconnectOnChangedSchema = node.isReconnectOnChangedSchema() == null
                 ? NetconfTopologyUtils.DEFAULT_RECONNECT_ON_CHANGED_SCHEMA : node.isReconnectOnChangedSchema();
 
-        RemoteDeviceHandler<NetconfSessionPreferences> salFacade =  new MasterSalFacade(remoteDeviceId,
+        RemoteDeviceHandler<NetconfSessionPreferences> salFacade = new MasterSalFacade(remoteDeviceId,
                 netconfTopologyDeviceSetup.getDomBroker(), netconfTopologyDeviceSetup.getBindingAwareBroker(),
                 netconfTopologyDeviceSetup.getActorSystem(), deviceContextActorRef);
         if (keepaliveDelay > 0) {
@@ -179,13 +179,13 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
         }
 
         // pre register yang library sources as fallback schemas to schema registry
-        List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources = Lists.newArrayList();
+        final List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources = Lists.newArrayList();
         if (node.getYangLibrary() != null) {
             final String yangLibURL = node.getYangLibrary().getYangLibraryUrl().getValue();
             final String yangLibUsername = node.getYangLibrary().getUsername();
             final String yangLigPassword = node.getYangLibrary().getPassword();
 
-            LibraryModulesSchemas libraryModulesSchemas;
+            final LibraryModulesSchemas libraryModulesSchemas;
             if (yangLibURL != null) {
                 if (yangLibUsername != null && yangLigPassword != null) {
                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
@@ -193,7 +193,7 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL);
                 }
 
-                for (Map.Entry<SourceIdentifier, URL> sourceIdentifierURLEntry :
+                for (final Map.Entry<SourceIdentifier, URL> sourceIdentifierURLEntry :
                         libraryModulesSchemas.getAvailableModels().entrySet()) {
                     registeredYangLibSources
                             .add(schemaRegistry.registerSchemaSource(
@@ -232,29 +232,33 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
         return new NetconfConnectorDTO(
                 userCapabilities.isPresent()
                         ? new NetconfDeviceCommunicator(
-                                remoteDeviceId, device, new UserPreferences(userCapabilities.get(),
-                                node.getYangModuleCapabilities().isOverride()), rpcMessageLimit) :
-                        new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit), salFacade);
+                        remoteDeviceId, device, new UserPreferences(userCapabilities.get(),
+                        node.getYangModuleCapabilities().isOverride(), node.getNonModuleCapabilities().isOverride()),
+                        rpcMessageLimit)
+                        : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit), salFacade);
     }
 
     private Optional<NetconfSessionPreferences> getUserCapabilities(final NetconfNode node) {
-        if (node.getYangModuleCapabilities() == null) {
+        if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
             return Optional.empty();
         }
+        final List<String> capabilities = new ArrayList<>();
 
-        final List<String> capabilities = node.getYangModuleCapabilities().getCapability();
-        if (capabilities == null || capabilities.isEmpty()) {
-            return Optional.empty();
+        if (node.getYangModuleCapabilities() != null) {
+            capabilities.addAll(node.getYangModuleCapabilities().getCapability());
         }
 
-        final NetconfSessionPreferences parsedOverrideCapabilities =
-                NetconfSessionPreferences.fromStrings(capabilities);
-        Preconditions.checkState(parsedOverrideCapabilities.getNonModuleCaps().isEmpty(), remoteDeviceId +
-                ": Capabilities to override can only contain module based capabilities, non-module capabilities "
-                        + "will be retrieved from the device, configured non-module capabilities: "
-                        + parsedOverrideCapabilities.getNonModuleCaps());
+        //non-module capabilities should not exist in yang module capabilities
+        final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
+        Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(), "List yang-module-capabilities/capability " +
+                "should contain only module based capabilities. Non-module capabilities used: " +
+                netconfSessionPreferences.getNonModuleCaps());
 
-        return Optional.of(parsedOverrideCapabilities);
+        if (node.getNonModuleCapabilities() != null) {
+            capabilities.addAll(node.getNonModuleCapabilities().getCapability());
+        }
+
+        return Optional.of(NetconfSessionPreferences.fromStrings(capabilities, CapabilityOrigin.UserDefined));
     }
 
     private NetconfDevice.SchemaResourcesDTO setupSchemaCacheDTO(final NodeId nodeId, final NetconfNode node) {
@@ -334,7 +338,7 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
     }
 
     //TODO: duplicate code
-    private InetSocketAddress getSocketAddress(final Host host, int port) {
+    private InetSocketAddress getSocketAddress(final Host host, final int port) {
         if (host.getDomainName() != null) {
             return new InetSocketAddress(host.getDomainName().getValue(), port);
         } else {
@@ -345,38 +349,9 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
         }
     }
 
-    private static final class TimedReconnectStrategyFactory implements ReconnectStrategyFactory {
-        private final Long connectionAttempts;
-        private final EventExecutor executor;
-        private final double sleepFactor;
-        private final int minSleep;
-
-        TimedReconnectStrategyFactory(final EventExecutor executor, final Long maxConnectionAttempts,
-                                      final int minSleep, final BigDecimal sleepFactor) {
-            if (maxConnectionAttempts != null && maxConnectionAttempts > 0) {
-                connectionAttempts = maxConnectionAttempts;
-            } else {
-                connectionAttempts = null;
-            }
-
-            this.sleepFactor = sleepFactor.doubleValue();
-            this.executor = executor;
-            this.minSleep = minSleep;
-        }
-
-        @Override
-        public ReconnectStrategy createReconnectStrategy() {
-            final Long maxSleep = null;
-            final Long deadline = null;
-
-            return new TimedReconnectStrategy(executor, minSleep,
-                    minSleep, sleepFactor, maxSleep, connectionAttempts, deadline);
-        }
-    }
-
     @VisibleForTesting
     NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
-                                                                   final NetconfNode node) {
+                                                           final NetconfNode node) {
 
         //setup default values since default value is not supported in mdsal
         final long clientConnectionTimeoutMillis = node.getConnectionTimeoutMillis() == null
@@ -422,4 +397,33 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
     Map<String, NetconfDevice.SchemaResourcesDTO> getSchemaResourcesDTOs() {
         return schemaResourcesDTOs;
     }
+
+    private static final class TimedReconnectStrategyFactory implements ReconnectStrategyFactory {
+        private final Long connectionAttempts;
+        private final EventExecutor executor;
+        private final double sleepFactor;
+        private final int minSleep;
+
+        TimedReconnectStrategyFactory(final EventExecutor executor, final Long maxConnectionAttempts,
+                                      final int minSleep, final BigDecimal sleepFactor) {
+            if (maxConnectionAttempts != null && maxConnectionAttempts > 0) {
+                connectionAttempts = maxConnectionAttempts;
+            } else {
+                connectionAttempts = null;
+            }
+
+            this.sleepFactor = sleepFactor.doubleValue();
+            this.executor = executor;
+            this.minSleep = minSleep;
+        }
+
+        @Override
+        public ReconnectStrategy createReconnectStrategy() {
+            final Long maxSleep = null;
+            final Long deadline = null;
+
+            return new TimedReconnectStrategy(executor, minSleep,
+                    minSleep, sleepFactor, maxSleep, connectionAttempts, deadline);
+        }
+    }
 }
index 4295bc7286ad0f754504a2d12c29a1cf0e763e5b..836d9bdb10b3524c95a8aa76dd26ad0aef3b571f 100644 (file)
@@ -20,6 +20,7 @@ import java.io.File;
 import java.math.BigDecimal;
 import java.net.InetSocketAddress;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -137,7 +138,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
      * Netconf mount.  Access to <code>schemaResourcesDTOs</code> should be surrounded by appropriate
      * synchronization locks.
      */
-    private static volatile Map<String, NetconfDevice.SchemaResourcesDTO> schemaResourcesDTOs = new HashMap<>();
+    private static final Map<String, NetconfDevice.SchemaResourcesDTO> schemaResourcesDTOs = new HashMap<>();
 
     // Initializes default constant instances for the case when the default schema repository
     // directory cache/schema is used.
@@ -192,13 +193,13 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
     }
 
     @Override
-    public ListenableFuture<NetconfDeviceCapabilities> connectNode(NodeId nodeId, Node configNode) {
+    public ListenableFuture<NetconfDeviceCapabilities> connectNode(final NodeId nodeId, final Node configNode) {
         LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, configNode);
         return setupConnection(nodeId, configNode);
     }
 
     @Override
-    public ListenableFuture<Void> disconnectNode(NodeId nodeId) {
+    public ListenableFuture<Void> disconnectNode(final NodeId nodeId) {
         LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue());
         if (!activeConnectors.containsKey(nodeId)) {
             return Futures.immediateFailedFuture(new IllegalStateException("Unable to disconnect device that is not connected"));
@@ -229,12 +230,12 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
 
         Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
             @Override
-            public void onSuccess(NetconfDeviceCapabilities result) {
+            public void onSuccess(final NetconfDeviceCapabilities result) {
                 LOG.debug("Connector for : " + nodeId.getValue() + " started succesfully");
             }
 
             @Override
-            public void onFailure(Throwable t) {
+            public void onFailure(final Throwable t) {
                 LOG.error("Connector for : " + nodeId.getValue() + " failed");
                 // remove this node from active connectors?
             }
@@ -250,11 +251,11 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
         final Long keepaliveDelay = node.getKeepaliveDelay() == null ? DEFAULT_KEEPALIVE_DELAY : node.getKeepaliveDelay();
         final Boolean reconnectOnChangedSchema = node.isReconnectOnChangedSchema() == null ? DEFAULT_RECONNECT_ON_CHANGED_SCHEMA : node.isReconnectOnChangedSchema();
 
-        IpAddress ipAddress = node.getHost().getIpAddress();
-        InetSocketAddress address = new InetSocketAddress(ipAddress.getIpv4Address() != null ?
+        final IpAddress ipAddress = node.getHost().getIpAddress();
+        final InetSocketAddress address = new InetSocketAddress(ipAddress.getIpv4Address() != null ?
                 ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue(),
                 node.getPort().getValue());
-        RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address);
+        final RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address);
 
         RemoteDeviceHandler<NetconfSessionPreferences> salFacade =
                 createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker);
@@ -265,13 +266,13 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
         }
 
         // pre register yang library sources as fallback schemas to schema registry
-        List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources = Lists.newArrayList();
+        final List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources = Lists.newArrayList();
         if (node.getYangLibrary() != null) {
             final String yangLibURL = node.getYangLibrary().getYangLibraryUrl().getValue();
             final String yangLibUsername = node.getYangLibrary().getUsername();
             final String yangLigPassword = node.getYangLibrary().getPassword();
 
-            LibraryModulesSchemas libraryModulesSchemas;
+            final LibraryModulesSchemas libraryModulesSchemas;
             if(yangLibURL != null) {
                 if(yangLibUsername != null && yangLigPassword != null) {
                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
@@ -279,7 +280,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL);
                 }
 
-                for (Map.Entry<SourceIdentifier, URL> sourceIdentifierURLEntry : libraryModulesSchemas.getAvailableModels().entrySet()) {
+                for (final Map.Entry<SourceIdentifier, URL> sourceIdentifierURLEntry : libraryModulesSchemas.getAvailableModels().entrySet()) {
                     registeredYangLibSources.
                             add(schemaRegistry.registerSchemaSource(
                                     new YangLibrarySchemaYangSourceProvider(remoteDeviceId, libraryModulesSchemas.getAvailableModels()),
@@ -304,7 +305,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
                     .build();
         }
 
-        final Optional<NetconfSessionPreferences> userCapabilities = getUserCapabilities(node);
+        final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
         final int rpcMessageLimit =
                 node.getConcurrentRpcLimit() == null ? DEFAULT_CONCURRENT_RPC_LIMIT : node.getConcurrentRpcLimit();
 
@@ -312,11 +313,9 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
             LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
         }
 
-        return new NetconfConnectorDTO(
-                userCapabilities.isPresent() ?
-                        new NetconfDeviceCommunicator(
-                                remoteDeviceId, device, new UserPreferences(userCapabilities.get(), node.getYangModuleCapabilities().isOverride()), rpcMessageLimit):
-                        new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit), salFacade);
+        return new NetconfConnectorDTO(userCapabilities.isPresent()
+                ? new NetconfDeviceCommunicator(remoteDeviceId, device, userCapabilities.get(), rpcMessageLimit)
+                : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit), salFacade);
     }
 
     protected NetconfDevice.SchemaResourcesDTO setupSchemaCacheDTO(final NodeId nodeId, final NetconfNode node) {
@@ -387,7 +386,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
         return new FilesystemSchemaSourceCache<>(schemaRegistry, YangTextSchemaSource.class, new File(relativeSchemaCacheDirectory));
     }
 
-    public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener, NetconfNode node) {
+    public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener, final NetconfNode node) {
 
         //setup default values since default value is not supported in mdsal
         final long clientConnectionTimeoutMillis = node.getConnectionTimeoutMillis() == null ? DEFAULT_CONNECTION_TIMEOUT_MILLIS : node.getConnectionTimeoutMillis();
@@ -426,7 +425,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
 
     protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker);
 
-    private InetSocketAddress getSocketAddress(final Host host, int port) {
+    private InetSocketAddress getSocketAddress(final Host host, final int port) {
         if(host.getDomainName() != null) {
             return new InetSocketAddress(host.getDomainName().getValue(), port);
         } else {
@@ -436,22 +435,35 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
         }
     }
 
-    private Optional<NetconfSessionPreferences> getUserCapabilities(final NetconfNode node) {
-        if(node.getYangModuleCapabilities() == null) {
+    private Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
+        // if none of yang-module-capabilities or non-module-capabilities is specified
+        // just return absent
+        if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
             return Optional.absent();
         }
 
-        final List<String> capabilities = node.getYangModuleCapabilities().getCapability();
-        if(capabilities == null || capabilities.isEmpty()) {
-            return Optional.absent();
+        final List<String> capabilities = new ArrayList<>();
+
+        boolean overrideYangModuleCaps = false;
+        if (node.getYangModuleCapabilities() != null) {
+            capabilities.addAll(node.getYangModuleCapabilities().getCapability());
+            overrideYangModuleCaps = node.getYangModuleCapabilities().isOverride();
         }
 
-        final NetconfSessionPreferences parsedOverrideCapabilities = NetconfSessionPreferences.fromStrings(capabilities, CapabilityOrigin.UserDefined);
-        Preconditions.checkState(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());
+        //non-module capabilities should not exist in yang module capabilities
+        final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
+        Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(), "List yang-module-capabilities/capability " +
+                "should contain only module based capabilities. Non-module capabilities used: " +
+                netconfSessionPreferences.getNonModuleCaps());
 
-        return Optional.of(parsedOverrideCapabilities);
+        boolean overrideNonModuleCaps = false;
+        if (node.getNonModuleCapabilities() != null) {
+            capabilities.addAll(node.getNonModuleCapabilities().getCapability());
+            overrideNonModuleCaps = node.getNonModuleCapabilities().isOverride();
+        }
+
+        return Optional.of(new UserPreferences(NetconfSessionPreferences.fromStrings(capabilities, CapabilityOrigin.UserDefined),
+                overrideYangModuleCaps, overrideNonModuleCaps));
     }
 
     private static final class TimedReconnectStrategyFactory implements ReconnectStrategyFactory {
@@ -510,5 +522,4 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
             facade.close();
         }
     }
-
 }
index 990b54e43dfc966fc3f08b4aa521c83783983e41..f2f748b3cf6ef14ffaa3dde82aea9b51bc7d4f3d 100644 (file)
@@ -23,6 +23,8 @@ import org.opendaylight.controller.sal.binding.api.BindingAwareConsumer;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.NonModuleCapabilities;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.NonModuleCapabilitiesBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.YangModuleCapabilities;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.YangModuleCapabilitiesBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
@@ -149,15 +151,23 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co
                 .setUsername(getUsername())
                 .setPassword(getPassword())
                 .build();
-        final YangModuleCapabilities capabilities;
+
+        YangModuleCapabilities moduleCapabilities = null;
         if (getYangModuleCapabilities() != null) {
-            capabilities = new YangModuleCapabilitiesBuilder()
+            moduleCapabilities = new YangModuleCapabilitiesBuilder()
                     .setOverride(getYangModuleCapabilities().getOverride())
                     .setCapability(getYangModuleCapabilities().getCapability())
                     .build();
-        } else {
-            capabilities = null;
         }
+
+        NonModuleCapabilities nonModuleCapabilities = null;
+        if(getNonModuleCapabilities() != null) {
+            nonModuleCapabilities = new NonModuleCapabilitiesBuilder()
+                    .setOverride(getNonModuleCapabilities().getOverride())
+                    .setCapability(getNonModuleCapabilities().getCapability())
+                    .build();
+        }
+
         final YangLibrary yangLibrary;
         if (getYangLibrary() != null) {
             yangLibrary = new YangLibraryBuilder()
@@ -182,7 +192,8 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co
                 .setSchemaCacheDirectory(getSchemaCacheDirectory())
                 .setSleepFactor(getSleepFactor())
                 .setTcpOnly(getTcpOnly())
-                .setYangModuleCapabilities(capabilities)
+                .setYangModuleCapabilities(moduleCapabilities)
+                .setNonModuleCapabilities(nonModuleCapabilities)
                 .setYangLibrary(yangLibrary)
                 .build();
         return new NodeBuilder()
index 520ec3dd27d1bb889ea1df64908d1fc1c8235623..d2f73ae468b37d52f7e706ce59fc0a0a94ed18b8 100644 (file)
@@ -50,7 +50,6 @@ import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.NetconfMessag
 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapabilityBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.unavailable.capabilities.UnavailableCapability;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -456,7 +455,7 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
                             .collect(Collectors.toList()));
 
                     capabilities.addNonModuleBasedCapabilities(remoteSessionCapabilities.getNonModuleCaps().stream().map(entry -> new AvailableCapabilityBuilder()
-                            .setCapability(entry).setCapabilityOrigin(AvailableCapability.CapabilityOrigin.DeviceAdvertised).build())
+                            .setCapability(entry).setCapabilityOrigin(remoteSessionCapabilities.getNonModuleBasedCapsOrigin().get(entry)).build())
                             .collect(Collectors.toList()));
 
                     handleSalInitializationSuccess(result, remoteSessionCapabilities, getDeviceSpecificRpc(result));
index a3a6b60228f6bc8686ff658a6656f9209d2b0acd..260beaf59a3b4cc50ffbc05455cd276d38c498ca 100644 (file)
@@ -108,16 +108,20 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
             LOG.trace("{}: Session advertised capabilities: {}", id,
                     netconfSessionPreferences);
 
-            if(overrideNetconfCapabilities.isPresent()) {
-                netconfSessionPreferences = overrideNetconfCapabilities.get().isOverride() ?
-                        netconfSessionPreferences.replaceModuleCaps(overrideNetconfCapabilities.get().getSessionPreferences()) :
-                        netconfSessionPreferences.addModuleCaps(overrideNetconfCapabilities.get().getSessionPreferences());
-                LOG.debug(
-                        "{}: Session capabilities overridden, capabilities that will be used: {}",
-                        id, netconfSessionPreferences);
+            if (overrideNetconfCapabilities.isPresent()) {
+                final NetconfSessionPreferences sessionPreferences = overrideNetconfCapabilities
+                        .get().getSessionPreferences();
+                netconfSessionPreferences = overrideNetconfCapabilities.get().moduleBasedCapsOverrided()
+                        ? netconfSessionPreferences.replaceModuleCaps(sessionPreferences)
+                        : netconfSessionPreferences.addModuleCaps(sessionPreferences);
+
+                netconfSessionPreferences = overrideNetconfCapabilities.get().nonModuleBasedCapsOverrided()
+                        ? netconfSessionPreferences.replaceNonModuleCaps(sessionPreferences)
+                        : netconfSessionPreferences.addNonModuleCaps(sessionPreferences);
+                LOG.debug("{}: Session capabilities overridden, capabilities that will be used: {}", id,
+                        netconfSessionPreferences);
             }
 
-
             remoteDevice.onRemoteSessionUp(netconfSessionPreferences, this);
             if (!firstConnectionFuture.isDone()) {
                 firstConnectionFuture.set(netconfSessionPreferences.getNetconfDeviceCapabilities());
@@ -361,8 +365,7 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener,
             return Futures.immediateFuture( createSessionDownRpcResult() );
         }
 
-        final Request req = new Request( new UncancellableFuture<RpcResult<NetconfMessage>>(true),
-                                         message );
+        final Request req = new Request(new UncancellableFuture<>(true), message);
         requests.add(req);
 
         session.sendMessage(req.request).addListener(new FutureListener<Void>() {
index 7fb1b05db32f32d96622e36fcbfdb6736921e17e..6525f019d767d233470381e0d7ec48268a6cef78 100644 (file)
@@ -15,10 +15,8 @@ import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import java.net.URI;
 import java.util.Collection;
 import java.util.HashMap;
@@ -72,9 +70,9 @@ public final class NetconfSessionPreferences {
     };
 
     private final Map<QName, CapabilityOrigin> moduleBasedCaps;
-    private final Set<String> nonModuleCaps;
+    private final Map<String, CapabilityOrigin> nonModuleCaps;
 
-    NetconfSessionPreferences(final Set<String> nonModuleCaps, final Map<QName, CapabilityOrigin> moduleBasedCaps) {
+    NetconfSessionPreferences(final Map<String, CapabilityOrigin> nonModuleCaps, final Map<QName, CapabilityOrigin> moduleBasedCaps) {
         this.nonModuleCaps = Preconditions.checkNotNull(nonModuleCaps);
         this.moduleBasedCaps = Preconditions.checkNotNull(moduleBasedCaps);
     }
@@ -88,12 +86,16 @@ public final class NetconfSessionPreferences {
     }
 
     public Set<String> getNonModuleCaps() {
+        return nonModuleCaps.keySet();
+    }
+
+    public Map<String, CapabilityOrigin> getNonModuleBasedCapsOrigin() {
         return nonModuleCaps;
     }
 
     // allows partial matches - assuming parameters are in the same order
     public boolean containsPartialNonModuleCapability(final String capability) {
-        final Iterator<String> iterator = nonModuleCaps.iterator();
+        final Iterator<String> iterator = getNonModuleCaps().iterator();
         while(iterator.hasNext()) {
             if (iterator.next().startsWith(capability)) {
                 LOG.trace("capability {} partially matches {}", capability, nonModuleCaps);
@@ -104,7 +106,7 @@ public final class NetconfSessionPreferences {
     }
 
     public boolean containsNonModuleCapability(final String capability) {
-        return nonModuleCaps.contains(capability);
+        return nonModuleCaps.containsKey(capability);
     }
 
     public boolean containsModuleCapability(final QName capability) {
@@ -156,7 +158,7 @@ public final class NetconfSessionPreferences {
         final Map<QName, CapabilityOrigin> mergedCaps = Maps.newHashMapWithExpectedSize(moduleBasedCaps.size() + netconfSessionModuleCapabilities.getModuleBasedCaps().size());
         mergedCaps.putAll(moduleBasedCaps);
         mergedCaps.putAll(netconfSessionModuleCapabilities.getModuleBasedCapsOrigin());
-        return new NetconfSessionPreferences(getNonModuleCaps(), mergedCaps);
+        return new NetconfSessionPreferences(getNonModuleBasedCapsOrigin(), mergedCaps);
     }
 
     /**
@@ -167,11 +169,39 @@ public final class NetconfSessionPreferences {
      * @return new instance of preferences with replaced module-based capabilities
      */
     public NetconfSessionPreferences replaceModuleCaps(final NetconfSessionPreferences netconfSessionPreferences) {
-        return new NetconfSessionPreferences(getNonModuleCaps(), netconfSessionPreferences.getModuleBasedCapsOrigin());
+        return new NetconfSessionPreferences(getNonModuleBasedCapsOrigin(), netconfSessionPreferences.getModuleBasedCapsOrigin());
+    }
+
+    public NetconfSessionPreferences replaceModuleCaps(final Map<QName, CapabilityOrigin> newModuleBasedCaps) {
+        return new NetconfSessionPreferences(getNonModuleBasedCapsOrigin(), newModuleBasedCaps);
+    }
+
+
+    /**
+     * Merge list of non-module based capabilities with current list of non-module based capabilities
+     *
+     * @param netconfSessionNonModuleCapabilities capabilities to merge into this
+     *
+     * @return new instance of preferences with merged non-module based capabilities
+     */
+    public NetconfSessionPreferences addNonModuleCaps(
+            final NetconfSessionPreferences netconfSessionNonModuleCapabilities) {
+        final Map<String, CapabilityOrigin> mergedCaps = Maps.newHashMapWithExpectedSize(
+                nonModuleCaps.size() + netconfSessionNonModuleCapabilities.getNonModuleCaps().size());
+        mergedCaps.putAll(getNonModuleBasedCapsOrigin());
+        mergedCaps.putAll(netconfSessionNonModuleCapabilities.getNonModuleBasedCapsOrigin());
+        return new NetconfSessionPreferences(mergedCaps, getModuleBasedCapsOrigin());
     }
 
-    public NetconfSessionPreferences replaceModuleCaps(Map<QName, CapabilityOrigin> newModuleBasedCaps) {
-        return new NetconfSessionPreferences(getNonModuleCaps(), newModuleBasedCaps);
+    /**
+     * Override current list of non-module based capabilities
+     *
+     * @param netconfSessionPreferences capabilities to override in this
+     *
+     * @return new instance of preferences with replaced non-module based capabilities
+     */
+    public NetconfSessionPreferences replaceNonModuleCaps(final NetconfSessionPreferences netconfSessionPreferences) {
+        return new NetconfSessionPreferences(netconfSessionPreferences.getNonModuleBasedCapsOrigin(), getModuleBasedCapsOrigin());
     }
 
     public static NetconfSessionPreferences fromNetconfSession(final NetconfClientSession session) {
@@ -191,11 +221,12 @@ public final class NetconfSessionPreferences {
         return fromStrings(capabilities, CapabilityOrigin.DeviceAdvertised);
     }
 
-    public static NetconfSessionPreferences fromStrings(final Collection<String> capabilities, CapabilityOrigin capabilityOrigin) {
+    public static NetconfSessionPreferences fromStrings(final Collection<String> capabilities, final CapabilityOrigin capabilityOrigin) {
         final Map<QName, CapabilityOrigin> moduleBasedCaps = new HashMap<>();
-        final Set<String> nonModuleCaps = Sets.newHashSet(capabilities);
+        final Map<String, CapabilityOrigin> nonModuleCaps = new HashMap<>();
 
         for (final String capability : capabilities) {
+            nonModuleCaps.put(capability, capabilityOrigin);
             final int qmark = capability.indexOf('?');
             if (qmark == -1) {
                 continue;
@@ -235,15 +266,15 @@ public final class NetconfSessionPreferences {
             addModuleQName(moduleBasedCaps, nonModuleCaps, capability, cachedQName(namespace, moduleName), capabilityOrigin);
         }
 
-        return new NetconfSessionPreferences(ImmutableSet.copyOf(nonModuleCaps), ImmutableMap.copyOf(moduleBasedCaps));
+        return new NetconfSessionPreferences(ImmutableMap.copyOf(nonModuleCaps), ImmutableMap.copyOf(moduleBasedCaps));
     }
 
-    private static void addModuleQName(final Map<QName, CapabilityOrigin> moduleBasedCaps, final Set<String> nonModuleCaps, final String capability, final QName qName, CapabilityOrigin capabilityOrigin) {
+    private static void addModuleQName(final Map<QName, CapabilityOrigin> moduleBasedCaps, final Map<String, CapabilityOrigin> nonModuleCaps, final String capability, final QName qName, final CapabilityOrigin capabilityOrigin) {
         moduleBasedCaps.put(qName, capabilityOrigin);
         nonModuleCaps.remove(capability);
     }
 
-    private NetconfDeviceCapabilities capabilities = new NetconfDeviceCapabilities();
+    private final NetconfDeviceCapabilities capabilities = new NetconfDeviceCapabilities();
 
     public NetconfDeviceCapabilities getNetconfDeviceCapabilities() {
         return capabilities;
index 640e41cb29a63166bea8bca6c09ddf084aefc29d..10742e0b5c4d4047c6c1150738b9335c58d70f86 100644 (file)
@@ -15,26 +15,46 @@ import javax.annotation.Nonnull;
 public class UserPreferences {
 
     private final NetconfSessionPreferences sessionPreferences;
-    private final boolean override;
+    private final boolean overrideModuleCapabilities;
+    private final boolean overrideNonModuleCapabilities;
+
+    public UserPreferences(@Nonnull final NetconfSessionPreferences sessionPreferences,
+            boolean overrideModuleCapabilities, boolean overrideNonModuleCapabilities) {
+
+        if (overrideModuleCapabilities && (sessionPreferences.getModuleBasedCaps() == null
+                || sessionPreferences.getModuleBasedCaps().isEmpty())) {
+            throw new IllegalStateException(
+                    "Override module based capabilities flag set true but module based capabilities list is empty.");
+        }
+        if (overrideNonModuleCapabilities && (sessionPreferences.getNonModuleCaps() == null
+                || sessionPreferences.getNonModuleCaps().isEmpty())) {
+            throw new IllegalStateException(
+                    "Override non-module based capabilities set true but non-module based capabilities list is empty.");
+        }
 
-    public UserPreferences(@Nonnull final NetconfSessionPreferences sessionPreferences, boolean override) {
         this.sessionPreferences = sessionPreferences;
-        this.override = override;
+        this.overrideModuleCapabilities = overrideModuleCapabilities;
+        this.overrideNonModuleCapabilities = overrideNonModuleCapabilities;
     }
 
     public NetconfSessionPreferences getSessionPreferences() {
         return sessionPreferences;
     }
 
-    public boolean isOverride() {
-        return override;
+    public boolean moduleBasedCapsOverrided() {
+        return overrideModuleCapabilities;
+    }
+
+    public boolean nonModuleBasedCapsOverrided() {
+        return overrideNonModuleCapabilities;
     }
 
     @Override
     public String toString() {
         final StringBuffer sb = new StringBuffer("UserPreferences{");
         sb.append("sessionPreferences=").append(sessionPreferences);
-        sb.append(", override=").append(override);
+        sb.append(", overrideModuleCapabilities=").append(overrideModuleCapabilities);
+        sb.append(", overrideNonModuleCapabilities=").append(overrideNonModuleCapabilities);
         sb.append('}');
         return sb.toString();
     }
index 169d41961638ae961fc8805c297f29f93a0c7e44..d52b93dd25b2e2490cf40ff791036451d9cf2b79 100644 (file)
@@ -66,6 +66,23 @@ module netconf-node-topology {
             }
         }
 
+        container non-module-capabilities {
+            config true;
+            leaf override {
+                type boolean;
+                default false;
+                description "Whether to override or merge this list of non-module based capabilities with non-module
+                             based capabilities from device";
+            }
+
+            leaf-list capability {
+                type string;
+                description "Set a list of non-module based capabilities to override or merge non-module capabilities
+                             provided in device's hello message. Can be used for devices that do not report or
+                             incorrectly report non-module based capabilities in their hello message";
+            }
+        }
+
         leaf reconnect-on-changed-schema {
             config true;
             type boolean;
index fa6a411954d25a620017687067a25ffb14b69c1d..ec5f31c1019e7ae2b6a96a6a733132f6aa34ae00 100644 (file)
@@ -93,6 +93,22 @@ module odl-sal-netconf-connector-cfg {
                 }
             }
 
+            container non-module-capabilities {
+                leaf override {
+                    type boolean;
+                    default false;
+                    description "Whether to override or merge this list of non-module based capabilities with non-module
+                                 based capabilities from device";
+                }
+
+                leaf-list capability {
+                    type string;
+                    description "Set a list of non-module based capabilities to override or merge non-module capabilities
+                                 provided in device's hello message. Can be used for devices that do not report or
+                                 incorrectly report non-module based capabilities in their hello message";
+                }
+            }
+
             leaf reconnect-on-changed-schema {
                 type boolean;
                 default false;
index d9dd0c5f36826e5216c144b1677991d667902870..bd98fd191ebb44df7dcccae24703f952f3fdec4b 100644 (file)
@@ -10,7 +10,9 @@ package org.opendaylight.netconf.sal.connect.netconf.listener;
 
 import static org.hamcrest.CoreMatchers.hasItem;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import com.google.common.collect.Lists;
 import java.util.List;
@@ -75,6 +77,76 @@ public class NetconfSessionPreferencesTest {
         assertCaps(replaced, 2, 2);
     }
 
+    @Test
+    public void testNonModuleMerge() throws Exception {
+        final List<String> caps1 = Lists.newArrayList(
+                "namespace:1?module=module1&revision=2012-12-12",
+                "namespace:2?module=module2&amp;revision=2012-12-12",
+                "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&amp;revision=2010-10-04",
+                "urn:ietf:params:netconf:base:1.0",
+                "urn:ietf:params:netconf:capability:rollback-on-error:1.0",
+                "urn:ietf:params:netconf:capability:candidate:1.0"
+        );
+        final NetconfSessionPreferences sessionCaps1 = NetconfSessionPreferences.fromStrings(caps1);
+        assertCaps(sessionCaps1, 3, 3);
+        assertTrue(sessionCaps1.isCandidateSupported());
+
+        final List<String> caps2 = Lists.newArrayList(
+                "namespace:3?module=module3&revision=2012-12-12",
+                "namespace:4?module=module4&revision=2012-12-12",
+                "urn:ietf:params:netconf:capability:writable-running:1.0",
+                "urn:ietf:params:netconf:capability:notification:1.0"
+        );
+        final NetconfSessionPreferences sessionCaps2 = NetconfSessionPreferences.fromStrings(caps2);
+        assertCaps(sessionCaps2, 2, 2);
+        assertTrue(sessionCaps2.isRunningWritable());
+
+        final NetconfSessionPreferences merged = sessionCaps1.addNonModuleCaps(sessionCaps2);
+
+        assertCaps(merged, 3+2, 3);
+        for (final String capability : sessionCaps2.getNonModuleCaps()) {
+            assertThat(merged.getNonModuleCaps(), hasItem(capability));
+        }
+
+        assertThat(merged.getNonModuleCaps(), hasItem("urn:ietf:params:netconf:base:1.0"));
+        assertThat(merged.getNonModuleCaps(), hasItem("urn:ietf:params:netconf:capability:rollback-on-error:1.0"));
+        assertThat(merged.getNonModuleCaps(), hasItem("urn:ietf:params:netconf:capability:writable-running:1.0"));
+        assertThat(merged.getNonModuleCaps(), hasItem("urn:ietf:params:netconf:capability:notification:1.0"));
+
+        assertTrue(merged.isCandidateSupported());
+        assertTrue(merged.isRunningWritable());
+    }
+
+    @Test
+    public void testNonmoduleReplace() throws Exception {
+        final List<String> caps1 = Lists.newArrayList(
+                "namespace:1?module=module1&revision=2012-12-12",
+                "namespace:2?module=module2&amp;revision=2012-12-12",
+                "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&amp;revision=2010-10-04",
+                "urn:ietf:params:netconf:base:1.0",
+                "urn:ietf:params:netconf:capability:rollback-on-error:1.0",
+                "urn:ietf:params:netconf:capability:candidate:1.0"
+        );
+        final NetconfSessionPreferences sessionCaps1 = NetconfSessionPreferences.fromStrings(caps1);
+        assertCaps(sessionCaps1, 3, 3);
+        assertTrue(sessionCaps1.isCandidateSupported());
+
+        final List<String> caps2 = Lists.newArrayList(
+                "namespace:3?module=module3&revision=2012-12-12",
+                "namespace:4?module=module4&revision=2012-12-12",
+                "randomNonModuleCap",
+                "urn:ietf:params:netconf:capability:writable-running:1.0"
+        );
+        final NetconfSessionPreferences sessionCaps2 = NetconfSessionPreferences.fromStrings(caps2);
+        assertCaps(sessionCaps2, 2, 2);
+        assertTrue(sessionCaps2.isRunningWritable());
+
+        final NetconfSessionPreferences replaced = sessionCaps1.replaceNonModuleCaps(sessionCaps2);
+        assertCaps(replaced, 2, 3);
+        assertFalse(replaced.isCandidateSupported());
+        assertTrue(replaced.isRunningWritable());
+    }
+
     @Test
     public void testCapabilityNoRevision() throws Exception {
         final List<String> caps1 = Lists.newArrayList(
index 81fe494e615508101e482caa97e69f55c2f6ad96..7b872968ce52e339d144589ce4c2bf9b580c072a 100644 (file)
@@ -66,6 +66,23 @@ module netconf-node-topology {
             }
         }
 
+        container non-module-capabilities {
+            config true;
+            leaf override {
+                type boolean;
+                default false;
+                description "Whether to override or merge this list of non-module based capabilities with non-module
+                             based capabilities from device";
+            }
+
+            leaf-list capability {
+                type string;
+                description "Set a list of non-module based capabilities to override or merge non-module capabilities
+                             provided in device's hello message. Can be used for devices that do not report or
+                             incorrectly report non-module based capabilities in their hello message";
+            }
+        }
+
         leaf reconnect-on-changed-schema {
             config true;
             type boolean;