From ab97282bcb83ee4510d18b33149bdd90c0863af3 Mon Sep 17 00:00:00 2001 From: Om Prakash Date: Tue, 30 Aug 2016 23:33:43 -0400 Subject: [PATCH] Bug-6346: Allow over-ride of non-module capabilities 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 Signed-off-by: Jakub Morvay --- .../schemas/netconf-node-topology.yang | 21 +++ .../impl/RemoteDeviceConnectorImpl.java | 128 +++++++++--------- .../topology/AbstractNetconfTopology.java | 71 ++++++---- .../netconf/NetconfConnectorModule.java | 21 ++- .../sal/connect/netconf/NetconfDevice.java | 3 +- .../listener/NetconfDeviceCommunicator.java | 23 ++-- .../listener/NetconfSessionPreferences.java | 61 +++++++-- .../netconf/listener/UserPreferences.java | 32 ++++- .../src/main/yang/netconf-node-topology.yang | 17 +++ .../yang/odl-sal-netconf-connector-cfg.yang | 16 +++ .../NetconfSessionPreferencesTest.java | 72 ++++++++++ .../schemas/netconf-node-topology.yang | 17 +++ 12 files changed, 352 insertions(+), 130 deletions(-) diff --git a/netconf/netconf-console/src/test/resources/schemas/netconf-node-topology.yang b/netconf/netconf-console/src/test/resources/schemas/netconf-node-topology.yang index 07461d48d7..d52b93dd25 100644 --- a/netconf/netconf-console/src/test/resources/schemas/netconf-node-topology.yang +++ b/netconf/netconf-console/src/test/resources/schemas/netconf-node-topology.yang @@ -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 { diff --git a/netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/RemoteDeviceConnectorImpl.java b/netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/RemoteDeviceConnectorImpl.java index 0525789d15..aa8853daa4 100644 --- a/netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/RemoteDeviceConnectorImpl.java +++ b/netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/RemoteDeviceConnectorImpl.java @@ -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 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() { @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 salFacade = new MasterSalFacade(remoteDeviceId, + RemoteDeviceHandler 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> registeredYangLibSources = Lists.newArrayList(); + final List> 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 sourceIdentifierURLEntry : + for (final Map.Entry 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 getUserCapabilities(final NetconfNode node) { - if (node.getYangModuleCapabilities() == null) { + if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) { return Optional.empty(); } + final List capabilities = new ArrayList<>(); - final List 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 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); + } + } } diff --git a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java index 4295bc7286..836d9bdb10 100644 --- a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java +++ b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java @@ -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 schemaResourcesDTOs should be surrounded by appropriate * synchronization locks. */ - private static volatile Map schemaResourcesDTOs = new HashMap<>(); + private static final Map 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 connectNode(NodeId nodeId, Node configNode) { + public ListenableFuture connectNode(final NodeId nodeId, final Node configNode) { LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, configNode); return setupConnection(nodeId, configNode); } @Override - public ListenableFuture disconnectNode(NodeId nodeId) { + public ListenableFuture 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() { @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 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> registeredYangLibSources = Lists.newArrayList(); + final List> 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 sourceIdentifierURLEntry : libraryModulesSchemas.getAvailableModels().entrySet()) { + for (final Map.Entry 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 userCapabilities = getUserCapabilities(node); + final Optional 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 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 getUserCapabilities(final NetconfNode node) { - if(node.getYangModuleCapabilities() == null) { + private Optional 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 capabilities = node.getYangModuleCapabilities().getCapability(); - if(capabilities == null || capabilities.isEmpty()) { - return Optional.absent(); + final List 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(); } } - } diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java index 990b54e43d..f2f748b3cf 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java @@ -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() diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java index 520ec3dd27..d2f73ae468 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java @@ -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 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)); diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfDeviceCommunicator.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfDeviceCommunicator.java index a3a6b60228..260beaf59a 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfDeviceCommunicator.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfDeviceCommunicator.java @@ -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>(true), - message ); + final Request req = new Request(new UncancellableFuture<>(true), message); requests.add(req); session.sendMessage(req.request).addListener(new FutureListener() { diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferences.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferences.java index 7fb1b05db3..6525f019d7 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferences.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferences.java @@ -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 moduleBasedCaps; - private final Set nonModuleCaps; + private final Map nonModuleCaps; - NetconfSessionPreferences(final Set nonModuleCaps, final Map moduleBasedCaps) { + NetconfSessionPreferences(final Map nonModuleCaps, final Map moduleBasedCaps) { this.nonModuleCaps = Preconditions.checkNotNull(nonModuleCaps); this.moduleBasedCaps = Preconditions.checkNotNull(moduleBasedCaps); } @@ -88,12 +86,16 @@ public final class NetconfSessionPreferences { } public Set getNonModuleCaps() { + return nonModuleCaps.keySet(); + } + + public Map getNonModuleBasedCapsOrigin() { return nonModuleCaps; } // allows partial matches - assuming parameters are in the same order public boolean containsPartialNonModuleCapability(final String capability) { - final Iterator iterator = nonModuleCaps.iterator(); + final Iterator 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 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 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 mergedCaps = Maps.newHashMapWithExpectedSize( + nonModuleCaps.size() + netconfSessionNonModuleCapabilities.getNonModuleCaps().size()); + mergedCaps.putAll(getNonModuleBasedCapsOrigin()); + mergedCaps.putAll(netconfSessionNonModuleCapabilities.getNonModuleBasedCapsOrigin()); + return new NetconfSessionPreferences(mergedCaps, getModuleBasedCapsOrigin()); } - public NetconfSessionPreferences replaceModuleCaps(Map 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 capabilities, CapabilityOrigin capabilityOrigin) { + public static NetconfSessionPreferences fromStrings(final Collection capabilities, final CapabilityOrigin capabilityOrigin) { final Map moduleBasedCaps = new HashMap<>(); - final Set nonModuleCaps = Sets.newHashSet(capabilities); + final Map 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 moduleBasedCaps, final Set nonModuleCaps, final String capability, final QName qName, CapabilityOrigin capabilityOrigin) { + private static void addModuleQName(final Map moduleBasedCaps, final Map 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; diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/UserPreferences.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/UserPreferences.java index 640e41cb29..10742e0b5c 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/UserPreferences.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/listener/UserPreferences.java @@ -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(); } diff --git a/netconf/sal-netconf-connector/src/main/yang/netconf-node-topology.yang b/netconf/sal-netconf-connector/src/main/yang/netconf-node-topology.yang index 169d419616..d52b93dd25 100644 --- a/netconf/sal-netconf-connector/src/main/yang/netconf-node-topology.yang +++ b/netconf/sal-netconf-connector/src/main/yang/netconf-node-topology.yang @@ -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; diff --git a/netconf/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang b/netconf/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang index fa6a411954..ec5f31c101 100644 --- a/netconf/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang +++ b/netconf/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang @@ -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; diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferencesTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferencesTest.java index d9dd0c5f36..bd98fd191e 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferencesTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/listener/NetconfSessionPreferencesTest.java @@ -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 caps1 = Lists.newArrayList( + "namespace:1?module=module1&revision=2012-12-12", + "namespace:2?module=module2&revision=2012-12-12", + "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04", + "urn:ietf:params:netconf:base:1.0", + "urn:ietf:params:netconf:capability:rollback-on-error:1.0", + "urn:ietf:params:netconf:capability:candidate:1.0" + ); + final NetconfSessionPreferences sessionCaps1 = NetconfSessionPreferences.fromStrings(caps1); + assertCaps(sessionCaps1, 3, 3); + assertTrue(sessionCaps1.isCandidateSupported()); + + final List 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 caps1 = Lists.newArrayList( + "namespace:1?module=module1&revision=2012-12-12", + "namespace:2?module=module2&revision=2012-12-12", + "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04", + "urn:ietf:params:netconf:base:1.0", + "urn:ietf:params:netconf:capability:rollback-on-error:1.0", + "urn:ietf:params:netconf:capability:candidate:1.0" + ); + final NetconfSessionPreferences sessionCaps1 = NetconfSessionPreferences.fromStrings(caps1); + assertCaps(sessionCaps1, 3, 3); + assertTrue(sessionCaps1.isCandidateSupported()); + + final List 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 caps1 = Lists.newArrayList( diff --git a/netconf/sal-netconf-connector/src/test/resources/schemas/netconf-node-topology.yang b/netconf/sal-netconf-connector/src/test/resources/schemas/netconf-node-topology.yang index 81fe494e61..7b872968ce 100644 --- a/netconf/sal-netconf-connector/src/test/resources/schemas/netconf-node-topology.yang +++ b/netconf/sal-netconf-connector/src/test/resources/schemas/netconf-node-topology.yang @@ -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; -- 2.36.6