From: Sangwook Ha Date: Thu, 14 Jul 2022 08:03:54 +0000 (-0700) Subject: Filter non-standard nodes from NETCONF monitoring schemas X-Git-Tag: v4.0.0~16 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=netconf.git;a=commitdiff_plain;h=c3d40c5e4485e95b9307fcdff1833ca1568f9321 Filter non-standard nodes from NETCONF monitoring schemas Some NETCONF servers use augmented netconf-monitoring schema and controller fails to parse XML with strict parsing requirement. Add a filtering function based on the namespace of the XML node, and filter out any node not in the netconf-monitoring namespace to prevent parsing failure. JIRA: NETCONF-881 Change-Id: I93b7da0baf00de59613d970fd4ec89e47eb26e58 Signed-off-by: Sangwook Ha Signed-off-by: Robert Varga --- diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfStateSchemas.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfStateSchemas.java index c94ecb424f..3061b8d170 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfStateSchemas.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfStateSchemas.java @@ -10,6 +10,7 @@ package org.opendaylight.netconf.sal.connect.netconf; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; +import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.IETF_NETCONF_MONITORING; import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DATA_NODEID; import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_FILTER_NODEID; import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_FILTER_QNAME; @@ -60,6 +61,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.traversal.DocumentTraversal; +import org.w3c.dom.traversal.NodeFilter; +import org.w3c.dom.traversal.TreeWalker; import org.xml.sax.SAXException; /** @@ -69,8 +74,9 @@ public final class NetconfStateSchemas implements NetconfDeviceSchemas { public static final NetconfStateSchemas EMPTY = new NetconfStateSchemas(ImmutableSet.of()); private static final Logger LOG = LoggerFactory.getLogger(NetconfStateSchemas.class); - private static final YangInstanceIdentifier STATE_SCHEMAS_IDENTIFIER = - YangInstanceIdentifier.builder().node(NetconfState.QNAME).node(Schemas.QNAME).build(); + private static final YangInstanceIdentifier STATE_SCHEMAS_IDENTIFIER = YangInstanceIdentifier.builder() + .node(NetconfState.QNAME).node(Schemas.QNAME).build(); + private static final String MONITORING_NAMESPACE = IETF_NETCONF_MONITORING.getNamespace().toString(); private static final @NonNull ContainerNode GET_SCHEMAS_RPC; static { @@ -185,32 +191,71 @@ public final class NetconfStateSchemas implements NetconfDeviceSchemas { if (result == null) { return Optional.empty(); } - final Optional rpcResultOpt = ((ContainerNode)result).findChildByArg(NETCONF_DATA_NODEID); + // FIXME: unchecked cast + final var rpcResultOpt = ((ContainerNode) result).findChildByArg(NETCONF_DATA_NODEID); if (rpcResultOpt.isEmpty()) { return Optional.empty(); } - final DataContainerChild rpcResult = rpcResultOpt.get(); + final var rpcResult = rpcResultOpt.get(); verify(rpcResult instanceof DOMSourceAnyxmlNode, "Unexpected result %s", rpcResult); - final NormalizedNode dataNode; + // Server may include additional data which we do not understand. Make sure we trim the input before we try + // to interpret it. + // FIXME: this is something NetconfUtil.transformDOMSourceToNormalizedNode(), and more generally, NormalizedNode + // codecs should handle. We really want to a NormalizedNode tree which can be directly queried for known + // things while completely ignoring XML content (and hence its semantics) of other elements. + final var filteredBody = ietfMonitoringCopy(((DOMSourceAnyxmlNode) rpcResult).body()); + + final NormalizedNode dataNode; try { - dataNode = NetconfUtil.transformDOMSourceToNormalizedNode(schemaContext, - ((DOMSourceAnyxmlNode) rpcResult).body()).getResult(); + dataNode = NetconfUtil.transformDOMSourceToNormalizedNode(schemaContext, filteredBody).getResult(); } catch (XMLStreamException | URISyntaxException | IOException | SAXException e) { LOG.warn("Failed to transform {}", rpcResult, e); return Optional.empty(); } - final Optional nStateNode = ((DataContainerNode) dataNode).findChildByArg( - toId(NetconfState.QNAME)); + // FIXME: unchecked cast + final var nStateNode = ((DataContainerNode) dataNode).findChildByArg(toId(NetconfState.QNAME)); if (nStateNode.isEmpty()) { return Optional.empty(); } + // FIXME: unchecked cast return ((DataContainerNode) nStateNode.get()).findChildByArg(toId(Schemas.QNAME)); } + @VisibleForTesting + static DOMSource ietfMonitoringCopy(final DOMSource domSource) { + final var sourceDoc = XmlUtil.newDocument(); + sourceDoc.appendChild(sourceDoc.importNode(domSource.getNode(), true)); + + final var treeWalker = ((DocumentTraversal) sourceDoc).createTreeWalker(sourceDoc.getDocumentElement(), + NodeFilter.SHOW_ALL, node -> { + final var namespace = node.getNamespaceURI(); + return namespace == null || MONITORING_NAMESPACE.equals(namespace) + ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; + }, false); + + final var filteredDoc = XmlUtil.newDocument(); + filteredDoc.appendChild(filteredDoc.importNode(treeWalker.getRoot(), false)); + final var filteredElement = filteredDoc.getDocumentElement(); + copyChildren(treeWalker, filteredDoc, filteredElement); + + return new DOMSource(filteredElement); + } + + private static void copyChildren(final TreeWalker walker, final Document targetDoc, final Node targetNode) { + if (walker.firstChild() != null) { + for (var node = walker.getCurrentNode(); node != null; node = walker.nextSibling()) { + final var importedNode = targetDoc.importNode(node, false); + targetNode.appendChild(importedNode); + copyChildren(walker, targetDoc, importedNode); + walker.setCurrentNode(node); + } + } + } + public static final class RemoteYangSchema { private final QName qname; diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NC881Test.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NC881Test.java new file mode 100644 index 0000000000..6fec7b41f1 --- /dev/null +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NC881Test.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.netconf.sal.connect.netconf; + +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import javax.xml.transform.dom.DOMSource; +import org.custommonkey.xmlunit.XMLUnit; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.netconf.api.xml.XmlUtil; +import org.xml.sax.SAXException; + +public class NC881Test { + @BeforeClass + public static void classSetUp() { + XMLUnit.setIgnoreWhitespace(true); + } + + @Test + public void testFilterDomNamespaces() throws IOException, SAXException { + final var source = XmlUtil.readXmlToDocument( + getClass().getResourceAsStream("/nc881/netconf-state.xml")); + final var expected = XmlUtil.readXmlToDocument( + getClass().getResourceAsStream("/nc881/netconf-state-filtered.xml")); + + final var filteredDom = NetconfStateSchemas.ietfMonitoringCopy(new DOMSource(source.getDocumentElement())); + final var filtered = XmlUtil.newDocument(); + filtered.appendChild(filtered.importNode(filteredDom.getNode(), true)); + + final var diff = XMLUnit.compareXML(filtered, expected); + assertTrue(diff.toString(), diff.similar()); + } +} diff --git a/netconf/sal-netconf-connector/src/test/resources/nc881/netconf-state-filtered.xml b/netconf/sal-netconf-connector/src/test/resources/nc881/netconf-state-filtered.xml new file mode 100644 index 0000000000..7a662f9a16 --- /dev/null +++ b/netconf/sal-netconf-connector/src/test/resources/nc881/netconf-state-filtered.xml @@ -0,0 +1,79 @@ + + + + + + urn:opendaylight:params:xml:ns:yang:controller:threadpool + NETCONF + threadpool + prefix:yang + 2013-04-09 + + + urn:opendaylight:params:xml:ns:yang:controller:logback:config + NETCONF + config-logging + prefix:yang + 2013-07-16 + + + urn:opendaylight:model:statistics:types + NETCONF + opendaylight-statistics-types + prefix:yang + 2013-09-25 + + + urn:opendaylight:params:xml:ns:yang:controller:md:sal:core:spi:config-dom-store + + NETCONF + opendaylight-config-dom-datastore + prefix:yang + 2014-06-17 + + + urn:opendaylight:flow:table:statistics + NETCONF + opendaylight-flow-table-statistics + prefix:yang + 2013-12-15 + + + urn:opendaylight:meter:service + NETCONF + sal-meter + prefix:yang + 2013-09-18 + + + urn:opendaylight:params:xml:ns:yang:controller:config:toaster-provider:impl + + NETCONF + toaster-provider-impl + prefix:yang + 2014-01-31 + + + urn:opendaylight:table:types + NETCONF + opendaylight-table-types + prefix:yang + 2013-10-26 + + + urn:opendaylight:table:service + NETCONF + sal-table + prefix:yang + 2013-10-26 + + + urn:opendaylight:params:xml:ns:yang:controller:shutdown + NETCONF + shutdown + prefix:yang + 2013-12-18 + + + + diff --git a/netconf/sal-netconf-connector/src/test/resources/nc881/netconf-state.xml b/netconf/sal-netconf-connector/src/test/resources/nc881/netconf-state.xml new file mode 100644 index 0000000000..310da14d58 --- /dev/null +++ b/netconf/sal-netconf-connector/src/test/resources/nc881/netconf-state.xml @@ -0,0 +1,89 @@ + + + + + + urn:opendaylight:params:xml:ns:yang:controller:threadpool + NETCONF + threadpool + prefix:yang + 2013-04-09 + 1.0.0 + + + urn:opendaylight:params:xml:ns:yang:controller:logback:config + NETCONF + config-logging + prefix:yang + 2013-07-16 + 1.0.0 + + + urn:opendaylight:model:statistics:types + NETCONF + opendaylight-statistics-types + prefix:yang + 2013-09-25 + 1.0.0 + + + urn:opendaylight:params:xml:ns:yang:controller:md:sal:core:spi:config-dom-store + + NETCONF + opendaylight-config-dom-datastore + prefix:yang + 2014-06-17 + 1.0.0 + + + urn:opendaylight:flow:table:statistics + NETCONF + opendaylight-flow-table-statistics + prefix:yang + 2013-12-15 + 1.0.0 + + + urn:opendaylight:meter:service + NETCONF + sal-meter + prefix:yang + 2013-09-18 + 1.0.0 + + + urn:opendaylight:params:xml:ns:yang:controller:config:toaster-provider:impl + + NETCONF + toaster-provider-impl + prefix:yang + 2014-01-31 + 1.0.0 + + + urn:opendaylight:table:types + NETCONF + opendaylight-table-types + prefix:yang + 2013-10-26 + 1.0.0 + + + urn:opendaylight:table:service + NETCONF + sal-table + prefix:yang + 2013-10-26 + 1.0.0 + + + urn:opendaylight:params:xml:ns:yang:controller:shutdown + NETCONF + shutdown + prefix:yang + 2013-12-18 + 1.0.0 + + + +