From: Jaroslav Tóth Date: Mon, 25 Jan 2021 15:00:20 +0000 (+0100) Subject: Fixed reading whole list/leaf-list using GET/GET-CONFIG RPC X-Git-Tag: v1.13.0~3 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=16e3af45bd9aa72f448e1de29c7336f35422ec45;p=netconf.git Fixed reading whole list/leaf-list using GET/GET-CONFIG RPC - The source of the issue was in skipping of empty list/leaf-list nodes in XMLStreamNormalizedNodeStreamWriter - the NETCONF filter structure was created correctly, but it wasn't correctly serialized into XML. However, in NETCONF, it is valid to read whole list/leaf-list - we must be able to build NormalizedNode structure from subtree-filter which "ends" by list/leaf-list. - Fixed by introduction EmptyListXmlWriter which is responsible for serialization of empty list/leaf-list. Other operations are delegated to XMLStreamNormalizedNodeStreamWriter implementation. JIRA: NETCONF-744 Change-Id: Id4b665aa45a397ab25a53e12f4eb7c1a6539d428 Signed-off-by: Jaroslav Tóth Signed-off-by: Robert Varga --- diff --git a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/EmptyListXmlWriter.java b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/EmptyListXmlWriter.java new file mode 100644 index 0000000000..ad85a541cf --- /dev/null +++ b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/EmptyListXmlWriter.java @@ -0,0 +1,104 @@ +/* + * Copyright © 2021 FRINX s.r.o. 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.util; + +import java.io.IOException; +import javax.xml.XMLConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.stream.ForwardingNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; + +/** + * Proxy {@link NormalizedNodeStreamWriter} that is responsible for serialization of empty leaf-list and list + * nodes to output {@link XMLStreamWriter} as empty XML elements. Other operations are proxied + * to delegated {@link NormalizedNodeStreamWriter}. + */ +final class EmptyListXmlWriter extends ForwardingNormalizedNodeStreamWriter { + private final NormalizedNodeStreamWriter delegatedWriter; + private final XMLStreamWriter xmlStreamWriter; + + private boolean isInsideEmptyList = false; + + EmptyListXmlWriter(final NormalizedNodeStreamWriter delegatedWriter, final XMLStreamWriter xmlStreamWriter) { + this.delegatedWriter = delegatedWriter; + this.xmlStreamWriter = xmlStreamWriter; + } + + @Override + protected NormalizedNodeStreamWriter delegate() { + return delegatedWriter; + } + + @Override + public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException { + if (childSizeHint == 0) { + writeEmptyElement(name); + } else { + super.startUnkeyedList(name, childSizeHint); + } + } + + @Override + public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException { + if (childSizeHint == 0) { + writeEmptyElement(name); + } else { + super.startMapNode(name, childSizeHint); + } + } + + @Override + public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException { + if (childSizeHint == 0) { + writeEmptyElement(name); + } else { + super.startOrderedMapNode(name, childSizeHint); + } + } + + @Override + public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException { + if (childSizeHint == 0) { + writeEmptyElement(name); + } else { + super.startLeafSet(name, childSizeHint); + } + } + + @Override + public void startOrderedLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException { + if (childSizeHint == 0) { + writeEmptyElement(name); + } else { + super.startOrderedLeafSet(name, childSizeHint); + } + } + + @Override + public void endNode() throws IOException { + if (isInsideEmptyList) { + isInsideEmptyList = false; + } else { + super.endNode(); + } + } + + private void writeEmptyElement(final NodeIdentifier identifier) throws IOException { + final QName nodeType = identifier.getNodeType(); + try { + xmlStreamWriter.writeEmptyElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), + nodeType.getNamespace().toString()); + } catch (XMLStreamException e) { + throw new IOException("Failed to serialize empty element to XML: " + identifier, e); + } + isInsideEmptyList = true; + } +} \ No newline at end of file diff --git a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/NetconfUtil.java b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/NetconfUtil.java index 5d5b65c1b5..24d73643ea 100644 --- a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/NetconfUtil.java +++ b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/NetconfUtil.java @@ -210,8 +210,9 @@ public final class NetconfUtil { final XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(result); try { - try (NormalizedNodeStreamWriter writer = - XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, context, schemaPath)) { + try (NormalizedNodeStreamWriter streamWriter = + XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, context, schemaPath); + EmptyListXmlWriter writer = new EmptyListXmlWriter(streamWriter, xmlWriter)) { final Iterator it = query.getPathArguments().iterator(); final PathArgument first = it.next(); StreamingContext.fromSchemaAndQNameChecked(context, first.getNodeType()).streamToWriter(writer, first, @@ -247,8 +248,9 @@ public final class NetconfUtil { final XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(result); try { - try (NormalizedNodeStreamWriter writer = XMLStreamNormalizedNodeStreamWriter.create( - xmlWriter, context, schemaPath)) { + try (NormalizedNodeStreamWriter streamWriter = + XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, context, schemaPath); + EmptyListXmlWriter writer = new EmptyListXmlWriter(streamWriter, xmlWriter)) { final PathArgument first = rootNode.element(); StreamingContext.fromSchemaAndQNameChecked(context, first.getNodeType()) .streamToWriter(writer, first, rootNode); 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 82172f2c9b..023ce4d0ae 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 @@ -8,11 +8,11 @@ package org.opendaylight.netconf.util; import static com.google.common.base.Preconditions.checkArgument; -import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -160,7 +160,7 @@ abstract class StreamingContext implements Identifiable< final Iterator others) throws IOException { verifyActualPathArgument(first); - emitElementStart(writer, first); + emitElementStart(writer, first, others.hasNext() ? 1 : 0); if (others.hasNext()) { final PathArgument childPath = others.next(); final StreamingContext childOp = getChildOperation(childPath); @@ -174,7 +174,8 @@ abstract class StreamingContext implements Identifiable< final PathNode subtree) throws IOException { verifyActualPathArgument(first); - emitElementStart(writer, first); + final Collection children = subtree.children(); + emitElementStart(writer, first, children.size()); for (final PathNode node : subtree.children()) { emitChildTreeNode(writer, node); } @@ -194,7 +195,8 @@ abstract class StreamingContext implements Identifiable< } } - abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException; + abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg, + int childSizeHint) throws IOException; @SuppressWarnings("checkstyle:illegalCatch") StreamingContext getChildOperation(final PathArgument childPath) { @@ -342,8 +344,9 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { - writer.startChoiceNode(getIdentifier(), UNKNOWN_SIZE); + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { + writer.startChoiceNode(getIdentifier(), childSizeHint); } } @@ -388,9 +391,10 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg; - writer.startMapEntryNode(identifier, UNKNOWN_SIZE); + writer.startMapEntryNode(identifier, childSizeHint); for (Entry entry : identifier.entrySet()) { writer.startLeafNode(new NodeIdentifier(entry.getKey())); @@ -411,8 +415,9 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { - writer.startUnkeyedListItem(getIdentifier(), UNKNOWN_SIZE); + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { + writer.startUnkeyedListItem(getIdentifier(), childSizeHint); } } @@ -427,8 +432,9 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { - writer.startContainerNode(getIdentifier(), UNKNOWN_SIZE); + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { + writer.startContainerNode(getIdentifier(), childSizeHint); } } @@ -457,8 +463,9 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { - writer.startOrderedLeafSet(getIdentifier(), UNKNOWN_SIZE); + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { + writer.startOrderedLeafSet(getIdentifier(), childSizeHint); } } @@ -468,8 +475,9 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { - writer.startLeafSet(getIdentifier(), UNKNOWN_SIZE); + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { + writer.startLeafSet(getIdentifier(), childSizeHint); } } @@ -485,7 +493,8 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { writer.startAugmentationNode(getIdentifier()); } } @@ -496,8 +505,9 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { - writer.startMapNode(getIdentifier(), UNKNOWN_SIZE); + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { + writer.startMapNode(getIdentifier(), childSizeHint); } } @@ -507,8 +517,9 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { - writer.startOrderedMapNode(getIdentifier(), UNKNOWN_SIZE); + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { + writer.startOrderedMapNode(getIdentifier(), childSizeHint); } } @@ -531,8 +542,9 @@ abstract class StreamingContext implements Identifiable< } @Override - void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException { - writer.startUnkeyedList(getIdentifier(), UNKNOWN_SIZE); + void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg, + final int childSizeHint) throws IOException { + writer.startUnkeyedList(getIdentifier(), childSizeHint); } } } 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 9a19d7f427..dc5d85d5af 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 @@ -32,7 +32,6 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -476,6 +475,52 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { + ""); } + @Test + public void testGetLeafList() throws IOException, SAXException { + final YangInstanceIdentifier path = YangInstanceIdentifier.create( + toId(NetconfState.QNAME), + toId(Capabilities.QNAME), + toId(QName.create(Capabilities.QNAME, "capability"))); + final DataContainerChild filter = toFilterStructure(path, SCHEMA); + final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME, + NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filter)); + + assertSimilarXml(netconfMessage, "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"); + } + + @Test + public void testGetList() throws IOException, SAXException { + final YangInstanceIdentifier path = YangInstanceIdentifier.create( + toId(NetconfState.QNAME), + toId(Datastores.QNAME), + toId(QName.create(Datastores.QNAME, "datastore"))); + final DataContainerChild filter = toFilterStructure(path, SCHEMA); + final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME, + NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filter)); + + assertSimilarXml(netconfMessage, "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"); + } + private static NetconfMessageTransformer getTransformer(final EffectiveModelContext schema) { return new NetconfMessageTransformer(new EmptyMountPointContext(schema), true, BASE_SCHEMAS.getBaseSchema()); } @@ -543,7 +588,7 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { @Test public void toActionRequestContainerTopLevelTest() { - List nodeIdentifiers = Collections.singletonList(NodeIdentifier.create(DEVICE_QNAME)); + List nodeIdentifiers = List.of(NodeIdentifier.create(DEVICE_QNAME)); DOMDataTreeIdentifier domDataTreeIdentifier = prepareDataTreeId(nodeIdentifiers); NormalizedNode payload = initInputAction(QName.create(DEVICE_QNAME, "start-at"), "now"); @@ -624,7 +669,7 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { List nodeIdentifiers = new ArrayList<>(); nodeIdentifiers.add(NodeIdentifier.create(SERVER_QNAME)); nodeIdentifiers.add(NodeIdentifierWithPredicates.of(SERVER_QNAME, serverNameQname, "testServer")); - nodeIdentifiers.add(new AugmentationIdentifier(Collections.singleton(APPLICATIONS_QNAME))); + nodeIdentifiers.add(new AugmentationIdentifier(Set.of(APPLICATIONS_QNAME))); nodeIdentifiers.add(NodeIdentifier.create(APPLICATIONS_QNAME)); nodeIdentifiers.add(NodeIdentifier.create(APPLICATION_QNAME)); nodeIdentifiers.add(NodeIdentifierWithPredicates.of(APPLICATION_QNAME, @@ -829,8 +874,8 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { final YangInstanceIdentifier datastoresField = YangInstanceIdentifier.create(toId(Datastores.QNAME)); // building filter structure and NETCONF message - final DataContainerChild filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of( - parentYiid, List.of(netconfStartTimeField, datastoresField))), SCHEMA); + final DataContainerChild filterStructure = toFilterStructure( + List.of(FieldsFilter.of(parentYiid, List.of(netconfStartTimeField, datastoresField))), SCHEMA); final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure)); @@ -861,8 +906,10 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { toId(Datastore.QNAME), NodeIdentifierWithPredicates.of(Datastore.QNAME), toId(Locks.QNAME)); // building filter structure and NETCONF message - final DataContainerChild filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of( - parentYiid, List.of(capabilitiesField, capabilityField, datastoreField, locksFields))), SCHEMA); + final DataContainerChild filterStructure = toFilterStructure( + List.of(FieldsFilter.of(parentYiid, + List.of(capabilitiesField, capabilityField, datastoreField, locksFields))), + SCHEMA); final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure)); @@ -886,8 +933,9 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { final YangInstanceIdentifier capabilitiesField = YangInstanceIdentifier.create(toId(Capabilities.QNAME)); // building filter structure and NETCONF message - final DataContainerChild filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of( - parentYiid, List.of(capabilitiesField, YangInstanceIdentifier.empty()))), SCHEMA); + final DataContainerChild filterStructure = toFilterStructure( + List.of(FieldsFilter.of(parentYiid, List.of(capabilitiesField, YangInstanceIdentifier.empty()))), + SCHEMA); final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure)); @@ -912,8 +960,8 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { toId(QName.create(Schema.QNAME, "namespace").intern())); // building filter structure and NETCONF message - final DataContainerChild filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of( - parentYiid, List.of(versionField, identifierField))), SCHEMA); + final DataContainerChild filterStructure = toFilterStructure( + List.of(FieldsFilter.of(parentYiid, List.of(versionField, identifierField))), SCHEMA); final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure)); @@ -991,8 +1039,8 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { toId(Session.QNAME), NodeIdentifierWithPredicates.of(Session.QNAME)); // building filter structure and NETCONF message - final DataContainerChild filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of( - parentYiid, List.of(datastoreListField, sessionListField))), SCHEMA); + final DataContainerChild filterStructure = toFilterStructure( + List.of(FieldsFilter.of(parentYiid, List.of(datastoreListField, sessionListField))), SCHEMA); final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure)); @@ -1026,8 +1074,8 @@ public class NetconfMessageTransformerTest extends AbstractBaseSchemasTest { toId(QName.create(Session.QNAME, "transport").intern())); // building filter structure and NETCONF message - final DataContainerChild filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of( - parentYiid, List.of(session1Field, session2TransportField))), SCHEMA); + final DataContainerChild filterStructure = toFilterStructure( + List.of(FieldsFilter.of(parentYiid, List.of(session1Field, session2TransportField))), SCHEMA); final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));