From cd51da02e50493e4f07f281287c0c57f9af4a7a0 Mon Sep 17 00:00:00 2001 From: Maros Marsalek Date: Wed, 30 Jul 2014 16:11:39 +0200 Subject: [PATCH] BUG-628 Allow configuration to override module based capabilities from remote netconf device Suitable for devices that do not provide module based capabilities in hello message (most likely do not support ietf-netconf-monitoring). User can place yang files into cache/schema folder and set the capabilities for these modules into config attribute for netconf-connector. Change-Id: I541ef08bb5c0328e293f9c732353282ffaf0474d Signed-off-by: Maros Marsalek --- .../netconf/NetconfConnectorModule.java | 42 ++++++++++++++-- .../sal/connect/netconf/NetconfDevice.java | 1 + .../listener/NetconfDeviceCommunicator.java | 45 +++++++++++------ .../listener/NetconfSessionCapabilities.java | 42 ++++++++++++---- .../netconf/sal/NetconfDeviceDataBroker.java | 2 +- .../sal/tx/NetconfDeviceReadOnlyTx.java | 25 +++++++--- .../sal/tx/NetconfDeviceWriteOnlyTx.java | 12 ++--- .../yang/odl-sal-netconf-connector-cfg.yang | 8 +++ .../NetconfDeviceCommunicatorTest.java | 46 ++++++++--------- .../NetconfSessionCapabilitiesTest.java | 50 +++++++++++++++++++ 10 files changed, 204 insertions(+), 69 deletions(-) create mode 100644 opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java index 037bfb4a82..b75df80f4e 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/config/yang/md/sal/connector/netconf/NetconfConnectorModule.java @@ -13,8 +13,10 @@ import static org.opendaylight.controller.config.api.JmxAttributeValidationExcep import java.io.File; import java.io.InputStream; import java.net.InetSocketAddress; +import java.util.List; import java.util.concurrent.ExecutorService; +import org.opendaylight.controller.config.api.JmxAttributeValidationException; import org.opendaylight.controller.netconf.client.NetconfClientDispatcher; import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.controller.netconf.client.conf.NetconfReconnectingClientConfiguration; @@ -24,6 +26,7 @@ import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; import org.opendaylight.controller.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.controller.sal.connect.netconf.NetconfDevice; import org.opendaylight.controller.sal.connect.netconf.listener.NetconfDeviceCommunicator; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; import org.opendaylight.controller.sal.connect.netconf.sal.NetconfDeviceSalFacade; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.controller.sal.core.api.Broker; @@ -40,6 +43,8 @@ import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Optional; + /** * */ @@ -49,6 +54,7 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co private static AbstractCachingSchemaSourceProvider GLOBAL_NETCONF_SOURCE_PROVIDER = null; private BundleContext bundleContext; + private Optional userCapabilities; public NetconfConnectorModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier, final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { super(identifier, dependencyResolver); @@ -82,9 +88,11 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co checkNotNull(getPassword(), passwordJmxAttribute); } + userCapabilities = getUserCapabilities(); + } - private boolean isHostAddressPresent(Host address) { + private boolean isHostAddressPresent(final Host address) { return address.getDomainName() != null || address.getIpAddress() != null && (address.getIpAddress().getIpv4Address() != null || address.getIpAddress().getIpv6Address() != null); } @@ -98,10 +106,14 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co final Broker domBroker = getDomRegistryDependency(); final BindingAwareBroker bindingBroker = getBindingRegistryDependency(); - final RemoteDeviceHandler salFacade = new NetconfDeviceSalFacade(id, domBroker, bindingBroker, bundleContext, globalProcessingExecutor); + final RemoteDeviceHandler salFacade + = new NetconfDeviceSalFacade(id, domBroker, bindingBroker, bundleContext, globalProcessingExecutor); final NetconfDevice device = NetconfDevice.createNetconfDevice(id, getGlobalNetconfSchemaProvider(), globalProcessingExecutor, salFacade); - final NetconfDeviceCommunicator listener = new NetconfDeviceCommunicator(id, device); + + final NetconfDeviceCommunicator listener = userCapabilities.isPresent() ? + new NetconfDeviceCommunicator(id, device, userCapabilities.get()) : new NetconfDeviceCommunicator(id, device); + final NetconfReconnectingClientConfiguration clientConfig = getClientConfig(listener); final NetconfClientDispatcher dispatcher = getClientDispatcherDependency(); @@ -116,6 +128,26 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co }; } + private Optional getUserCapabilities() { + if(getYangModuleCapabilities() == null) { + return Optional.absent(); + } + + final List capabilities = getYangModuleCapabilities().getCapability(); + if(capabilities == null || capabilities.isEmpty()) { + return Optional.absent(); + } + + final NetconfSessionCapabilities parsedOverrideCapabilities = NetconfSessionCapabilities.fromStrings(capabilities); + JmxAttributeValidationException.checkCondition( + parsedOverrideCapabilities.getNonModuleCaps().isEmpty(), + "Capabilities to override can only contain module based capabilities, non-module capabilities will be retrieved from the device," + + " configured non-module capabilities: " + parsedOverrideCapabilities.getNonModuleCaps(), + yangModuleCapabilitiesJmxAttribute); + + return Optional.of(parsedOverrideCapabilities); + } + private synchronized AbstractCachingSchemaSourceProvider getGlobalNetconfSchemaProvider() { if(GLOBAL_NETCONF_SOURCE_PROVIDER == null) { final String storageFile = "cache/schema"; @@ -175,8 +207,8 @@ public final class NetconfConnectorModule extends org.opendaylight.controller.co if(getAddress().getDomainName() != null) { return new InetSocketAddress(getAddress().getDomainName().getValue(), getPort().getValue()); } else { - IpAddress ipAddress = getAddress().getIpAddress(); - String ip = ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue(); + final IpAddress ipAddress = getAddress().getIpAddress(); + final String ip = ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue(); return new InetSocketAddress(ip, getPort().getValue()); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java index de4ac7ac18..07d3c08774 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/NetconfDevice.java @@ -118,6 +118,7 @@ public final class NetconfDevice implements RemoteDevice { private static final Logger logger = LoggerFactory.getLogger(NetconfDeviceCommunicator.class); private final RemoteDevice remoteDevice; + private final Optional overrideNetconfCapabilities; private final RemoteDeviceId id; private final Lock sessionLock = new ReentrantLock(); + private final Queue requests = new ArrayDeque<>(); + private NetconfClientSession session; + + public NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, + final NetconfSessionCapabilities netconfSessionCapabilities) { + this(id, remoteDevice, Optional.of(netconfSessionCapabilities)); + } + public NetconfDeviceCommunicator(final RemoteDeviceId id, - final RemoteDevice remoteDevice) { + final RemoteDevice remoteDevice) { + this(id, remoteDevice, Optional.absent()); + } + + private NetconfDeviceCommunicator(final RemoteDeviceId id, final RemoteDevice remoteDevice, + final Optional overrideNetconfCapabilities) { this.id = id; this.remoteDevice = remoteDevice; + this.overrideNetconfCapabilities = overrideNetconfCapabilities; } - private final Queue requests = new ArrayDeque<>(); - private NetconfClientSession session; - @Override public void onSessionUp(final NetconfClientSession session) { sessionLock.lock(); @@ -68,10 +78,15 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, logger.debug("{}: Session established", id); this.session = session; - final NetconfSessionCapabilities netconfSessionCapabilities = + NetconfSessionCapabilities netconfSessionCapabilities = NetconfSessionCapabilities.fromNetconfSession(session); logger.trace("{}: Session advertised capabilities: {}", id, netconfSessionCapabilities); + if(overrideNetconfCapabilities.isPresent()) { + netconfSessionCapabilities = netconfSessionCapabilities.replaceModuleCaps(overrideNetconfCapabilities.get()); + logger.debug("{}: Session capabilities overridden, capabilities that will be used: {}", id, netconfSessionCapabilities); + } + remoteDevice.onRemoteSessionUp(netconfSessionCapabilities, this); } finally { @@ -223,7 +238,7 @@ public class NetconfDeviceCommunicator implements NetconfClientSessionListener, return; } - request.future.set( RpcResultBuilder.success( message ).build() ); + request.future.set( RpcResultBuilder.success( message ).build() ); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java index 8964a80228..7800ae69ef 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java @@ -8,6 +8,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -19,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class NetconfSessionCapabilities { + private static final class ParameterMatcher { private final Predicate predicate; private final int skipLength; @@ -57,10 +59,10 @@ public final class NetconfSessionCapabilities { }; private final Set moduleBasedCaps; - private final Set capabilities; + private final Set nonModuleCaps; - private NetconfSessionCapabilities(final Set capabilities, final Set moduleBasedCaps) { - this.capabilities = Preconditions.checkNotNull(capabilities); + private NetconfSessionCapabilities(final Set nonModuleCaps, final Set moduleBasedCaps) { + this.nonModuleCaps = Preconditions.checkNotNull(nonModuleCaps); this.moduleBasedCaps = Preconditions.checkNotNull(moduleBasedCaps); } @@ -68,30 +70,45 @@ public final class NetconfSessionCapabilities { return moduleBasedCaps; } - public boolean containsCapability(final String capability) { - return capabilities.contains(capability); + public Set getNonModuleCaps() { + return nonModuleCaps; + } + + public boolean containsNonModuleCapability(final String capability) { + return nonModuleCaps.contains(capability); } - public boolean containsCapability(final QName capability) { + public boolean containsModuleCapability(final QName capability) { return moduleBasedCaps.contains(capability); } @Override public String toString() { return Objects.toStringHelper(this) - .add("capabilities", capabilities) + .add("capabilities", nonModuleCaps) + .add("moduleBasedCapabilities", moduleBasedCaps) .add("rollback", isRollbackSupported()) .add("monitoring", isMonitoringSupported()) .toString(); } public boolean isRollbackSupported() { - return containsCapability(NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()); + return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()); } public boolean isMonitoringSupported() { - return containsCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING) - || containsCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); + return containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING) + || containsNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); + } + + public NetconfSessionCapabilities replaceModuleCaps(final NetconfSessionCapabilities netconfSessionModuleCapabilities) { + final Set moduleBasedCaps = Sets.newHashSet(netconfSessionModuleCapabilities.getModuleBasedCaps()); + + // Preserve monitoring module, since it indicates support for ietf-netconf-monitoring + if(containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING)) { + moduleBasedCaps.add(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING); + } + return new NetconfSessionCapabilities(getNonModuleCaps(), moduleBasedCaps); } public static NetconfSessionCapabilities fromNetconfSession(final NetconfClientSession session) { @@ -100,6 +117,7 @@ public final class NetconfSessionCapabilities { public static NetconfSessionCapabilities fromStrings(final Collection capabilities) { final Set moduleBasedCaps = new HashSet<>(); + final Set nonModuleCaps = Sets.newHashSet(capabilities); for (final String capability : capabilities) { final int qmark = capability.indexOf('?'); @@ -117,6 +135,7 @@ public final class NetconfSessionCapabilities { String revision = REVISION_PARAM.from(queryParams); if (revision != null) { moduleBasedCaps.add(QName.create(namespace, revision, moduleName)); + nonModuleCaps.remove(capability); continue; } @@ -136,8 +155,9 @@ public final class NetconfSessionCapabilities { // FIXME: do we really want to continue here? moduleBasedCaps.add(QName.create(namespace, revision, moduleName)); + nonModuleCaps.remove(capability); } - return new NetconfSessionCapabilities(ImmutableSet.copyOf(capabilities), ImmutableSet.copyOf(moduleBasedCaps)); + return new NetconfSessionCapabilities(ImmutableSet.copyOf(nonModuleCaps), ImmutableSet.copyOf(moduleBasedCaps)); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java index ee0c8b7217..817fc2994d 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java @@ -42,7 +42,7 @@ final class NetconfDeviceDataBroker implements DOMDataBroker { @Override public DOMDataReadOnlyTransaction newReadOnlyTransaction() { - return new NetconfDeviceReadOnlyTx(rpc, normalizer); + return new NetconfDeviceReadOnlyTx(rpc, normalizer, id); } @Override diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java index 3248453baf..03ee2d62f3 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java @@ -15,6 +15,7 @@ import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessag import com.google.common.base.Function; import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; @@ -22,6 +23,7 @@ import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizat import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.controller.sal.core.api.RpcImplementation; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; @@ -38,10 +40,12 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction private final RpcImplementation rpc; private final DataNormalizer normalizer; + private final RemoteDeviceId id; - public NetconfDeviceReadOnlyTx(final RpcImplementation rpc, final DataNormalizer normalizer) { + public NetconfDeviceReadOnlyTx(final RpcImplementation rpc, final DataNormalizer normalizer, final RemoteDeviceId id) { this.rpc = rpc; this.normalizer = normalizer; + this.id = id; } public ListenableFuture>> readConfigurationData(final YangInstanceIdentifier path) { @@ -51,6 +55,8 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction return Futures.transform(future, new Function, Optional>>() { @Override public Optional> apply(final RpcResult result) { + checkReadSuccess(result, path); + final CompositeNode data = result.getResult().getFirstCompositeByName(NETCONF_DATA_QNAME); final CompositeNode node = (CompositeNode) findNode(data, path); @@ -61,6 +67,11 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction }); } + private void checkReadSuccess(final RpcResult result, final YangInstanceIdentifier path) { + LOG.warn("{}: Unable to read data: {}, errors: {}", id, path, result.getErrors()); + Preconditions.checkArgument(result.isSuccessful(), "%s: Unable to read data: %s, errors: %s", id, path, result.getErrors()); + } + private Optional> transform(final YangInstanceIdentifier path, final CompositeNode node) { if(node == null) { return Optional.absent(); @@ -68,7 +79,7 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction try { return Optional.>of(normalizer.toNormalized(path, node).getValue()); } catch (final Exception e) { - LOG.error("Unable to normalize data for {}, data: {}", path, node, e); + LOG.error("{}: Unable to normalize data for {}, data: {}", id, path, node, e); throw e; } } @@ -79,6 +90,8 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction return Futures.transform(future, new Function, Optional>>() { @Override public Optional> apply(final RpcResult result) { + checkReadSuccess(result, path); + final CompositeNode data = result.getResult().getFirstCompositeByName(NETCONF_DATA_QNAME); final CompositeNode node = (CompositeNode) findNode(data, path); @@ -123,7 +136,7 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction @Override public ListenableFuture>> read(final LogicalDatastoreType store, final YangInstanceIdentifier path) { - final YangInstanceIdentifier legacyPath = toLegacyPath(normalizer, path); + final YangInstanceIdentifier legacyPath = toLegacyPath(normalizer, path, id); switch (store) { case CONFIGURATION : { @@ -134,14 +147,14 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction } } - throw new IllegalArgumentException(String.format("Cannot read data %s for %s datastore, unknown datastore type", path, store)); + throw new IllegalArgumentException(String.format("%s, Cannot read data %s for %s datastore, unknown datastore type", id, path, store)); } - static YangInstanceIdentifier toLegacyPath(final DataNormalizer normalizer, final YangInstanceIdentifier path) { + static YangInstanceIdentifier toLegacyPath(final DataNormalizer normalizer, final YangInstanceIdentifier path, final RemoteDeviceId id) { try { return normalizer.toLegacy(path); } catch (final DataNormalizationException e) { - throw new IllegalArgumentException("Cannot normalize path " + path, e); + throw new IllegalArgumentException(id + ": Cannot normalize path " + path, e); } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java index c8d9028210..6f6e814119 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java @@ -100,10 +100,10 @@ public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction { @Override public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { - Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store); + Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can replaceModuleCaps only configuration, not %s", store); try { - final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path); + final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id); final CompositeNode legacyData = normalizer.toLegacy(path, data); sendEditRpc(createEditConfigStructure(legacyPath, Optional.of(ModifyAction.REPLACE), Optional.fromNullable(legacyData)), Optional.of(ModifyAction.NONE)); } catch (final ExecutionException e) { @@ -115,10 +115,10 @@ public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction { @Override public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { - Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store); + Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can replaceModuleCaps only configuration, not %s", store); try { - final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path); + final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id); final CompositeNode legacyData = normalizer.toLegacy(path, data); sendEditRpc( createEditConfigStructure(legacyPath, Optional. absent(), Optional.fromNullable(legacyData)), Optional. absent()); @@ -131,10 +131,10 @@ public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction { @Override public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) { - Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store); + Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can replaceModuleCaps only configuration, not %s", store); try { - sendEditRpc(createEditConfigStructure(NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path), Optional.of(ModifyAction.DELETE), Optional.absent()), Optional.of(ModifyAction.NONE)); + sendEditRpc(createEditConfigStructure(NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id), Optional.of(ModifyAction.DELETE), Optional.absent()), Optional.of(ModifyAction.NONE)); } catch (final ExecutionException e) { LOG.warn("Error deleting data {}, discarding changes", path, e); discardChanges(); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang index 6bad4798c2..e13398b1df 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/yang/odl-sal-netconf-connector-cfg.yang @@ -58,6 +58,14 @@ module odl-sal-netconf-connector-cfg { type string; } + container yang-module-capabilities { + leaf-list capability { + type string; + description "Set a list of capabilities to override capabilities provided in device's hello message. + Can be used for devices that do not report any yang modules in their hello message"; + } + } + container dom-registry { uses config:service-ref { refine type { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java index 391bf9c6a4..001b9a8d3a 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfDeviceCommunicatorTest.java @@ -8,35 +8,35 @@ package org.opendaylight.controller.sal.connect.netconf.listener; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY; +import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0; + +import com.google.common.base.Strings; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ListenableFuture; import io.netty.channel.ChannelFuture; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; - import java.io.ByteArrayInputStream; import java.util.Collection; import java.util.Collections; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; - -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.same; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY; -import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0; - import org.apache.commons.lang3.StringUtils; import org.junit.Before; import org.junit.Test; @@ -56,10 +56,6 @@ import org.opendaylight.yangtools.yang.common.RpcResult; import org.w3c.dom.Document; import org.w3c.dom.Element; -import com.google.common.base.Strings; -import com.google.common.collect.Sets; -import com.google.common.util.concurrent.ListenableFuture; - public class NetconfDeviceCommunicatorTest { @Mock @@ -129,9 +125,9 @@ public class NetconfDeviceCommunicatorTest { verify( mockDevice ).onRemoteSessionUp( netconfSessionCapabilities.capture(), eq( communicator ) ); NetconfSessionCapabilities actualCapabilites = netconfSessionCapabilities.getValue(); - assertEquals( "containsCapability", true, actualCapabilites.containsCapability( - NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString() ) ); - assertEquals( "containsCapability", true, actualCapabilites.containsCapability( testCapability ) ); + assertEquals( "containsModuleCapability", true, actualCapabilites.containsNonModuleCapability( + NetconfMessageTransformUtil.NETCONF_ROLLBACK_ON_ERROR_URI.toString()) ); + assertEquals( "containsModuleCapability", false, actualCapabilites.containsNonModuleCapability(testCapability) ); assertEquals( "getModuleBasedCaps", Sets.newHashSet( QName.create( "urn:opendaylight:params:xml:ns:test", "2014-06-02", "test-module" )), actualCapabilites.getModuleBasedCaps() ); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java new file mode 100644 index 0000000000..87947b57fa --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilitiesTest.java @@ -0,0 +1,50 @@ +package org.opendaylight.controller.sal.connect.netconf.listener; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import com.google.common.collect.Lists; +import java.util.List; +import org.junit.Test; +import org.junit.matchers.JUnitMatchers; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; +import org.opendaylight.yangtools.yang.common.QName; + +public class NetconfSessionCapabilitiesTest { + + @Test + public void testMerge() throws Exception { + final List caps1 = Lists.newArrayList( + "namespace:1?module=module1&revision=2012-12-12", + "namespace:2?module=module2&revision=2012-12-12", + "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04", + "urn:ietf:params:netconf:base:1.0", + "urn:ietf:params:netconf:capability:rollback-on-error:1.0" + ); + final NetconfSessionCapabilities sessionCaps1 = NetconfSessionCapabilities.fromStrings(caps1); + assertCaps(sessionCaps1, 2, 3); + + final List caps2 = Lists.newArrayList( + "namespace:3?module=module3&revision=2012-12-12", + "namespace:4?module=module4&revision=2012-12-12", + "randomNonModuleCap" + ); + final NetconfSessionCapabilities sessionCaps2 = NetconfSessionCapabilities.fromStrings(caps2); + assertCaps(sessionCaps2, 1, 2); + + final NetconfSessionCapabilities merged = sessionCaps1.replaceModuleCaps(sessionCaps2); + assertCaps(merged, 2, 2 + 1 /*Preserved monitoring*/); + for (final QName qName : sessionCaps2.getModuleBasedCaps()) { + assertThat(merged.getModuleBasedCaps(), JUnitMatchers.hasItem(qName)); + } + assertThat(merged.getModuleBasedCaps(), JUnitMatchers.hasItem(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING)); + + assertThat(merged.getNonModuleCaps(), JUnitMatchers.hasItem("urn:ietf:params:netconf:base:1.0")); + assertThat(merged.getNonModuleCaps(), JUnitMatchers.hasItem("urn:ietf:params:netconf:capability:rollback-on-error:1.0")); + } + + private void assertCaps(final NetconfSessionCapabilities sessionCaps1, final int nonModuleCaps, final int moduleCaps) { + assertEquals(nonModuleCaps, sessionCaps1.getNonModuleCaps().size()); + assertEquals(moduleCaps, sessionCaps1.getModuleBasedCaps().size()); + } +} -- 2.36.6