Filter non-standard nodes from NETCONF monitoring schemas 50/101850/5
authorSangwook Ha <sangwook.ha@verizon.com>
Thu, 14 Jul 2022 08:03:54 +0000 (01:03 -0700)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 17 Jul 2022 22:15:14 +0000 (00:15 +0200)
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 <sangwook.ha@verizon.com>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfStateSchemas.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NC881Test.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/resources/nc881/netconf-state-filtered.xml [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/resources/nc881/netconf-state.xml [new file with mode: 0644]

index c94ecb424fb7f5db755f9c61600d960b51ab0d07..3061b8d17052d4a19ec0dbe2d2cd083f583302e9 100644 (file)
@@ -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<DataContainerChild> 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<DataContainerChild> 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 (file)
index 0000000..6fec7b4
--- /dev/null
@@ -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 (file)
index 0000000..7a662f9
--- /dev/null
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
+    <schemas>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:threadpool</namespace>
+        <location>NETCONF</location>
+        <identifier>threadpool</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-04-09</version>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:logback:config</namespace>
+        <location>NETCONF</location>
+        <identifier>config-logging</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-07-16</version>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:model:statistics:types</namespace>
+        <location>NETCONF</location>
+        <identifier>opendaylight-statistics-types</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-09-25</version>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:md:sal:core:spi:config-dom-store
+        </namespace>
+        <location>NETCONF</location>
+        <identifier>opendaylight-config-dom-datastore</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2014-06-17</version>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:flow:table:statistics</namespace>
+        <location>NETCONF</location>
+        <identifier>opendaylight-flow-table-statistics</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-12-15</version>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:meter:service</namespace>
+        <location>NETCONF</location>
+        <identifier>sal-meter</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-09-18</version>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:config:toaster-provider:impl
+        </namespace>
+        <location>NETCONF</location>
+        <identifier>toaster-provider-impl</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2014-01-31</version>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:table:types</namespace>
+        <location>NETCONF</location>
+        <identifier>opendaylight-table-types</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-10-26</version>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:table:service</namespace>
+        <location>NETCONF</location>
+        <identifier>sal-table</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-10-26</version>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:shutdown</namespace>
+        <location>NETCONF</location>
+        <identifier>shutdown</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-12-18</version>
+      </schema>
+    </schemas>
+  </netconf-state>
+</data>
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 (file)
index 0000000..310da14
--- /dev/null
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+  <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
+    <schemas>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:threadpool</namespace>
+        <location>NETCONF</location>
+        <identifier>threadpool</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-04-09</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:logback:config</namespace>
+        <location>NETCONF</location>
+        <identifier>config-logging</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-07-16</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:model:statistics:types</namespace>
+        <location>NETCONF</location>
+        <identifier>opendaylight-statistics-types</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-09-25</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:md:sal:core:spi:config-dom-store
+        </namespace>
+        <location>NETCONF</location>
+        <identifier>opendaylight-config-dom-datastore</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2014-06-17</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:flow:table:statistics</namespace>
+        <location>NETCONF</location>
+        <identifier>opendaylight-flow-table-statistics</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-12-15</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:meter:service</namespace>
+        <location>NETCONF</location>
+        <identifier>sal-meter</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-09-18</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:config:toaster-provider:impl
+        </namespace>
+        <location>NETCONF</location>
+        <identifier>toaster-provider-impl</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2014-01-31</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:table:types</namespace>
+        <location>NETCONF</location>
+        <identifier>opendaylight-table-types</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-10-26</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:table:service</namespace>
+        <location>NETCONF</location>
+        <identifier>sal-table</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-10-26</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+      <schema>
+        <namespace>urn:opendaylight:params:xml:ns:yang:controller:shutdown</namespace>
+        <location>NETCONF</location>
+        <identifier>shutdown</identifier>
+        <format xmlns:prefix="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">prefix:yang</format>
+        <version>2013-12-18</version>
+        <semver xmlns="urn:opendaylight:params:xml:ns:netconf">1.0.0</semver>
+      </schema>
+    </schemas>
+  </netconf-state>
+</data>