From: Jaroslav Tóth Date: Sun, 25 Oct 2020 07:43:08 +0000 (+0100) Subject: Explicit reading of list keys using subtree filtering X-Git-Tag: v1.13.0~5 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=91a9f1b584e1f5d6dd3c30d3d23773ab71acb46b;p=netconf.git Explicit reading of list keys using subtree filtering - According to RFC-6241, section 6.2.5, NETCONF server doesn't have to provide values of list keys, if NETCONF client doesn't ask for these fields; see following statement: o If any sibling nodes of the selection node are instance identifier components for a conceptual data structure (e.g., list key leaf), then they MAY also be included in the filter output. JIRA: NETCONF-735 Change-Id: I9bb048010578fd89190f616664e2175c3d3326ae Signed-off-by: Jaroslav Tóth Signed-off-by: Robert Varga --- diff --git a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/StreamingContext.java b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/StreamingContext.java index 5cf6a7e732..82172f2c9b 100644 --- a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/StreamingContext.java +++ b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/StreamingContext.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.stream.Collectors; import javax.xml.transform.dom.DOMSource; import org.opendaylight.yangtools.concepts.Identifiable; import org.opendaylight.yangtools.yang.common.QName; @@ -169,19 +170,22 @@ abstract class StreamingContext implements Identifiable< } @Override - void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first, - final PathNode subtree) throws IOException { + final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first, + final PathNode subtree) throws IOException { verifyActualPathArgument(first); emitElementStart(writer, first); for (final PathNode node : subtree.children()) { - final PathArgument childPath = node.element(); - final StreamingContext childOp = getChildOperation(childPath); - childOp.streamToWriter(writer, childPath, node); + emitChildTreeNode(writer, node); } writer.endNode(); } + void emitChildTreeNode(final NormalizedNodeStreamWriter writer, final PathNode node) throws IOException { + final PathArgument childPath = node.element(); + getChildOperation(childPath).streamToWriter(writer, childPath, node); + } + private void verifyActualPathArgument(final PathArgument first) { if (!isMixin()) { final QName type = getIdentifier().getNodeType(); @@ -193,7 +197,7 @@ abstract class StreamingContext implements Identifiable< abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException; @SuppressWarnings("checkstyle:illegalCatch") - private StreamingContext getChildOperation(final PathArgument childPath) { + StreamingContext getChildOperation(final PathArgument childPath) { final StreamingContext childOp; try { childOp = getChild(childPath); @@ -238,10 +242,12 @@ abstract class StreamingContext implements Identifiable< private abstract static class AbstractMapMixin extends AbstractComposite { private final ListEntry innerNode; + private final List keyLeaves; AbstractMapMixin(final ListSchemaNode list) { super(NodeIdentifier.create(list.getQName())); this.innerNode = new ListEntry(NodeIdentifierWithPredicates.of(list.getQName()), list); + this.keyLeaves = list.getKeyDefinition(); } @Override @@ -253,6 +259,24 @@ abstract class StreamingContext implements Identifiable< final boolean isMixin() { return true; } + + @Override + final void emitChildTreeNode(final NormalizedNodeStreamWriter writer, final PathNode node) throws IOException { + final NodeIdentifierWithPredicates childPath = (NodeIdentifierWithPredicates) node.element(); + final StreamingContext childOp = getChildOperation(childPath); + if (childPath.size() == 0 && node.isEmpty() || childPath.keySet().containsAll(keyLeaves)) { + // This is a query for the entire list, or the query specifies everything we need + childOp.streamToWriter(writer, childPath, node); + return; + } + + // Inexact query, we need to also request the leaf nodes we need to for reconstructing a valid instance + // NodeIdentifierWithPredicates. + childOp.streamToWriter(writer, childPath, node.copyWith(keyLeaves.stream() + .filter(qname -> !childPath.containsKey(qname)) + .map(NodeIdentifier::new) + .collect(Collectors.toUnmodifiableList()))); + } } private abstract static class AbstractSimple extends StreamingContext { @@ -271,7 +295,7 @@ abstract class StreamingContext implements Identifiable< } @Override - void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first, + final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first, final PathNode tree) throws IOException { streamToWriter(writer, first, Collections.emptyIterator()); } diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java index 2bfea94396..e2f3878b46 100644 --- a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java +++ b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java @@ -69,6 +69,7 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.mon import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Statistics; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.datastores.Datastore; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.datastores.datastore.Locks; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.datastores.datastore.locks.lock.type.partial.lock.PartialLock; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.Schema; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.sessions.Session; import org.opendaylight.yangtools.rcf8528.data.util.EmptyMountPointContext; @@ -908,7 +909,7 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { final YangInstanceIdentifier versionField = YangInstanceIdentifier.create( toId(QName.create(Schema.QNAME, "version").intern())); final YangInstanceIdentifier identifierField = YangInstanceIdentifier.create( - toId(QName.create(Schema.QNAME, "identifier").intern())); + toId(QName.create(Schema.QNAME, "namespace").intern())); // building filter structure and NETCONF message final DataContainerChild filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of( @@ -924,7 +925,10 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { + "\n" + "\n" + "\n" + + "\n" + // explicitly fetched list keys - identifier and format + "\n" + + "\n" + "\n" + "\n" + "\n" @@ -933,6 +937,49 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { + ""); } + @Test + public void getSpecificFieldsUnderMultipleLists() throws IOException, SAXException { + // preparation of the fields + final YangInstanceIdentifier parentYiid = YangInstanceIdentifier.create( + toId(NetconfState.QNAME), toId(Datastores.QNAME)); + final YangInstanceIdentifier partialLockYiid = YangInstanceIdentifier.create(toId(Datastore.QNAME), + NodeIdentifierWithPredicates.of(Datastore.QNAME), toId(Locks.QNAME), + toId(QName.create(Locks.QNAME, "lock-type").intern()), toId(PartialLock.QNAME), + NodeIdentifierWithPredicates.of(PartialLock.QNAME)); + final YangInstanceIdentifier lockedTimeField = partialLockYiid.node( + QName.create(Locks.QNAME, "locked-time").intern()); + final YangInstanceIdentifier lockedBySessionField = partialLockYiid.node( + QName.create(Locks.QNAME, "locked-by-session").intern()); + + // building filter structure and NETCONF message + final DataContainerChild filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of( + parentYiid, List.of(lockedTimeField, lockedBySessionField))), SCHEMA); + final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME, + NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure)); + + // testing + assertSimilarXml(netconfMessage, "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""); + } + @Test public void getWholeListsUsingFieldsTest() throws IOException, SAXException { // preparation of the fields