import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
}
}
+ /**
+ * Writing subtree filter specified by {@link YangInstanceIdentifier} into {@link DOMResult}.
+ *
+ * @param query path to the root node
+ * @param result DOM result holder
+ * @param schemaPath schema path of the parent node
+ * @param context mountpoint schema context
+ * @throws IOException failed to write filter into {@link NormalizedNodeStreamWriter}
+ * @throws XMLStreamException failed to serialize filter into XML document
+ */
public static void writeFilter(final YangInstanceIdentifier query, final DOMResult result,
final SchemaPath schemaPath, final EffectiveModelContext context) throws IOException, XMLStreamException {
if (query.isEmpty()) {
}
}
+ /**
+ * Writing subtree filter specified by parent {@link YangInstanceIdentifier} and specific fields
+ * into {@link DOMResult}. Field paths are relative to parent query path.
+ *
+ * @param query path to the root node
+ * @param result DOM result holder
+ * @param schemaPath schema path of the parent node
+ * @param context mountpoint schema context
+ * @param fields list of specific fields for which the filter should be created
+ * @throws IOException failed to write filter into {@link NormalizedNodeStreamWriter}
+ * @throws XMLStreamException failed to serialize filter into XML document
+ * @throws NullPointerException if any argument is null
+ */
+ public static void writeFilter(final YangInstanceIdentifier query, final DOMResult result,
+ final SchemaPath schemaPath, final EffectiveModelContext context,
+ final List<YangInstanceIdentifier> fields) throws IOException, XMLStreamException {
+ if (query.isEmpty() || fields.isEmpty()) {
+ // No query at all
+ return;
+ }
+ final List<YangInstanceIdentifier> aggregatedFields = aggregateFields(fields);
+ final PathNode rootNode = constructPathArgumentTree(query, aggregatedFields);
+
+ final XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(result);
+ try {
+ try (NormalizedNodeStreamWriter writer = XMLStreamNormalizedNodeStreamWriter.create(
+ xmlWriter, context, schemaPath)) {
+ final PathArgument first = rootNode.element();
+ StreamingContext.fromSchemaAndQNameChecked(context, first.getNodeType())
+ .streamToWriter(writer, first, rootNode);
+ }
+ } finally {
+ xmlWriter.close();
+ }
+ }
+
+ private static List<YangInstanceIdentifier> aggregateFields(final List<YangInstanceIdentifier> fields) {
+ return fields.stream()
+ .filter(field -> fields.stream()
+ .filter(fieldYiid -> !field.equals(fieldYiid))
+ .noneMatch(fieldYiid -> fieldYiid.contains(field)))
+ .collect(Collectors.toList());
+ }
+
+ private static PathNode constructPathArgumentTree(final YangInstanceIdentifier query,
+ final List<YangInstanceIdentifier> fields) {
+ final Iterator<PathArgument> queryIterator = query.getPathArguments().iterator();
+ final PathNode rootTreeNode = new PathNode(queryIterator.next());
+
+ PathNode queryTreeNode = rootTreeNode;
+ while (queryIterator.hasNext()) {
+ queryTreeNode = queryTreeNode.ensureChild(queryIterator.next());
+ }
+
+ for (final YangInstanceIdentifier field : fields) {
+ PathNode actualFieldTreeNode = queryTreeNode;
+ for (final PathArgument fieldPathArg : field.getPathArguments()) {
+ actualFieldTreeNode = actualFieldTreeNode.ensureChild(fieldPathArg);
+ }
+ }
+ return rootTreeNode;
+ }
+
public static NormalizedNodeResult transformDOMSourceToNormalizedNode(final MountPointContext mountContext,
final DOMSource value) throws XMLStreamException, URISyntaxException, IOException, SAXException {
final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
--- /dev/null
+/*
+ * Copyright © 2020 FRINX s.r.o. All rights reserved.
+ * Copyright © 2021 PANTHEON.tech, s.r.o.
+ *
+ * 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 static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.Maps;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.yangtools.concepts.Mutable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+
+/**
+ * Representation of the tree node with possible multiple child nodes. Child nodes are identified uniquely by path
+ * argument.
+ */
+@NonNullByDefault
+final class PathNode implements Mutable {
+ private final PathArgument argument;
+
+ private Map<PathArgument, PathNode> children;
+
+ private PathNode(final PathArgument argument, final LinkedHashMap<PathArgument, PathNode> children) {
+ this.argument = requireNonNull(argument);
+ this.children = requireNonNull(children);
+ }
+
+ /**
+ * Creation of tree node using a path argument.
+ *
+ * @param argument Path argument
+ */
+ PathNode(final PathArgument argument) {
+ this.argument = requireNonNull(argument);
+ this.children = Map.of();
+ }
+
+ /**
+ * Get path argument.
+ *
+ * @return path argument
+ */
+ PathArgument element() {
+ return argument;
+ }
+
+ /**
+ * Return current child nodes.
+ *
+ * @return Current child nodes
+ */
+ Collection<PathNode> children() {
+ return children.values();
+ }
+
+ /**
+ * Return {@code true} if this node has no child nodes.
+ *
+ * @return {@code true} if this node has no child nodes
+ */
+ boolean isEmpty() {
+ return children.isEmpty();
+ }
+
+ /**
+ * Create a copy of this node with specified immediate child nodes appended.
+ *
+ * @param childArguments Child arguments
+ * @return A copy of this {@link PathNode}
+ * @throws NullPointerException if {@code childArguments} is, or contains, {@code null}
+ */
+ PathNode copyWith(final Collection<PathArgument> childArguments) {
+ final LinkedHashMap<PathArgument, PathNode> copy = children instanceof LinkedHashMap
+ ? new LinkedHashMap<>(children) : Maps.newLinkedHashMapWithExpectedSize(childArguments.size());
+ for (PathArgument childArgument : childArguments) {
+ ensureChild(copy, childArgument);
+ }
+ return new PathNode(argument, copy);
+ }
+
+ /**
+ * Ensure a node for specified argument exists.
+ *
+ * @param childArgument Child argument
+ * @return A child {@link PathNode}
+ * @throws NullPointerException if {@code childArgument} is null
+ */
+ PathNode ensureChild(final PathArgument childArgument) {
+ return ensureChild(mutableChildren(), childArgument);
+ }
+
+ private static PathNode ensureChild(final LinkedHashMap<PathArgument, PathNode> children,
+ final PathArgument childArgument) {
+ return children.computeIfAbsent(requireNonNull(childArgument), PathNode::new);
+ }
+
+ private LinkedHashMap<PathArgument, PathNode> mutableChildren() {
+ final Map<PathArgument, PathNode> local = children;
+ if (local instanceof LinkedHashMap) {
+ return (LinkedHashMap<PathArgument, PathNode>) local;
+ }
+
+ // TODO: LinkedHashMap is rather heavy, do we need to retain insertion order?
+ final LinkedHashMap<PathArgument, PathNode> ret = new LinkedHashMap<>(4);
+ children = ret;
+ return ret;
+ }
+}
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.io.IOException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
abstract StreamingContext<?> getChild(PathArgument child);
+ /**
+ * Writing node structure that is described by series of {@link PathArgument}
+ * into {@link NormalizedNodeStreamWriter}.
+ *
+ * @param writer output {@link NormalizedNode} writer
+ * @param first the first {@link PathArgument}
+ * @param others iterator that points to next path arguments
+ * @throws IOException failed to write a stream of path arguments into {@link NormalizedNodeStreamWriter}
+ */
abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, Iterator<PathArgument> others)
throws IOException;
+ /**
+ * Writing node structure that is described by provided {@link PathNode} into {@link NormalizedNodeStreamWriter}.
+ *
+ * @param writer output {@link NormalizedNode} writer
+ * @param first the first {@link PathArgument}
+ * @param tree subtree of path arguments that starts with the first path argument
+ * @throws IOException failed to write a stream of path arguments into {@link NormalizedNodeStreamWriter}
+ */
+ abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, PathNode tree)
+ throws IOException;
+
abstract boolean isMixin();
private static Optional<DataSchemaNode> findChildSchemaNode(final DataNodeContainer parent, final QName child) {
@Override
final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
final Iterator<PathArgument> others) throws IOException {
- if (!isMixin()) {
- final QName type = getIdentifier().getNodeType();
- if (type != null) {
- final QName firstType = first.getNodeType();
- checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
- }
- }
+ verifyActualPathArgument(first);
emitElementStart(writer, first);
if (others.hasNext()) {
writer.endNode();
}
+ @Override
+ 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);
+ }
+ writer.endNode();
+ }
+
+ private void verifyActualPathArgument(final PathArgument first) {
+ if (!isMixin()) {
+ final QName type = getIdentifier().getNodeType();
+ final QName firstType = first.getNodeType();
+ checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
+ }
+ }
+
abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException;
@SuppressWarnings("checkstyle:illegalCatch")
final boolean isMixin() {
return false;
}
+
+ @Override
+ void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
+ final PathNode tree) throws IOException {
+ streamToWriter(writer, first, Collections.emptyIterator());
+ }
}
private static final class AnyXml extends AbstractSimple<NodeIdentifier> {
--- /dev/null
+<rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <get-config>
+ <source>
+ <running/>
+ </source>
+ <filter xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:type="subtree">
+ <c xmlns="test:namespace">
+ <a/>
+ <b/>
+ </c>
+ </filter>
+ </get-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <get-config>
+ <source>
+ <running/>
+ </source>
+ <filter xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:type="subtree">
+ <c xmlns="test:namespace">
+ <a/>
+ <d>
+ <x/>
+ </d>
+ </c>
+ <e xmlns="test:namespace">
+ <z/>
+ </e>
+ </filter>
+ </get-config>
+</rpc>
\ No newline at end of file
--- /dev/null
+<rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <get>
+ <filter xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:type="subtree">
+ <c xmlns="test:namespace">
+ <a/>
+ <b/>
+ </c>
+ </filter>
+ </get>
+</rpc>
\ No newline at end of file
--- /dev/null
+<rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <get>
+ <filter xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:type="subtree">
+ <c xmlns="test:namespace"/>
+ <e xmlns="test:namespace"/>
+ </filter>
+ </get>
+</rpc>
\ No newline at end of file
--- /dev/null
+<rpc message-id="m-0" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
+ <get>
+ <filter xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" ns0:type="subtree">
+ <c xmlns="test:namespace">
+ <a/>
+ <d>
+ <x/>
+ </d>
+ </c>
+ <e xmlns="test:namespace">
+ <z/>
+ </e>
+ </filter>
+ </get>
+</rpc>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright © 2020 FRINX s.r.o. All rights reserved.
+ * Copyright © 2021 PANTHEON.tech, s.r.o.
+ *
+ * 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.util;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.Immutable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+/**
+ * Definition of the subtree filter with single parent path and possibly multiple field sub-paths that are used
+ * for reading/selection of specific entities.
+ */
+@Beta
+public final class FieldsFilter implements Immutable {
+ private final @NonNull YangInstanceIdentifier path;
+ private final @NonNull List<YangInstanceIdentifier> fields;
+
+ private FieldsFilter(final YangInstanceIdentifier path, final List<YangInstanceIdentifier> fields) {
+ this.path = requireNonNull(path);
+ this.fields = ImmutableList.copyOf(fields);
+ }
+
+ /**
+ * Create a {@link FieldsFilter} using parent path and fields. Field paths are relative to parent path.
+ *
+ * @param path parent query path
+ * @param fields list of specific selection fields
+ * @return instance of {@link FieldsFilter}
+ * @throws NullPointerException if any argument is null, or if {@code fields} contains a null element
+ */
+ public static @NonNull FieldsFilter of(final YangInstanceIdentifier path,
+ final List<YangInstanceIdentifier> fields) {
+ return new FieldsFilter(path, fields);
+ }
+
+ /**
+ * Get parent path.
+ *
+ * @return instance of {@link YangInstanceIdentifier}
+ */
+ public @NonNull YangInstanceIdentifier path() {
+ return path;
+ }
+
+ /**
+ * Get list of paths that narrows the filter for specific fields. Field paths are relative to parent path.
+ *
+ * @return {@link List} of field paths.
+ */
+ public @NonNull List<YangInstanceIdentifier> fields() {
+ return fields;
+ }
+}
\ No newline at end of file
import static java.util.Objects.requireNonNull;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.COMMIT_RPC_CONTENT;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.EDIT_CONTENT_NODEID;
+import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.GET_RPC_CONTENT;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_NODEID;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toFilterStructure;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toId;
+import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.dom.api.DOMRpcResult;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
return future;
}
+ private ListenableFuture<? extends DOMRpcResult> getConfig(final FutureCallback<DOMRpcResult> callback,
+ final QName datastore, final Optional<YangInstanceIdentifier> filterPath,
+ final List<YangInstanceIdentifier> fields) {
+ requireNonNull(callback);
+ requireNonNull(datastore);
+
+ final NormalizedNode<?, ?> rpcInput;
+ if (isFilterPresent(filterPath)) {
+ final DataContainerChild<?, ?> node = transformer.toFilterStructure(
+ Collections.singletonList(FieldsFilter.of(filterPath.get(), fields)));
+ rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, getSourceNode(datastore), node);
+ } else if (containsEmptyPath(fields)) {
+ rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID, getSourceNode(datastore));
+ } else {
+ final DataContainerChild<?, ?> subtreeFilter = getSubtreeFilterFromRootFields(fields);
+ rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_NODEID,
+ getSourceNode(datastore), subtreeFilter);
+ }
+ final ListenableFuture<? extends DOMRpcResult> response = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME, rpcInput);
+ Futures.addCallback(response, callback, MoreExecutors.directExecutor());
+ return response;
+ }
+
+ /**
+ * Calling GET-CONFIG RPC with subtree filter that is specified by {@link YangInstanceIdentifier}.
+ *
+ * @param callback RPC response callback
+ * @param filterPath path to requested data
+ * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
+ */
public ListenableFuture<Optional<NormalizedNode<?, ?>>> getConfigRunningData(
final FutureCallback<DOMRpcResult> callback, final Optional<YangInstanceIdentifier> filterPath) {
return extractData(filterPath, getConfigRunning(callback, filterPath));
}
+ /**
+ * Calling GET-CONFIG RPC with subtree filter tha tis specified by parent {@link YangInstanceIdentifier} and list
+ * of specific fields that caller would like to read. Field paths are relative to parent path.
+ *
+ * @param callback RPC response callback
+ * @param filterPath parent path to requested data
+ * @param fields paths to specific fields that are selected under parent path
+ * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
+ */
public ListenableFuture<Optional<NormalizedNode<?, ?>>> getConfigRunningData(
final FutureCallback<DOMRpcResult> callback, final Optional<YangInstanceIdentifier> filterPath,
final List<YangInstanceIdentifier> fields) {
- // FIXME: implement this method
- throw new UnsupportedOperationException();
+ if (fields.isEmpty()) {
+ // RFC doesn't allow to build subtree filter that would expect just empty element in response
+ return Futures.immediateFailedFuture(new IllegalArgumentException(
+ "Failed to build NETCONF GET-CONFIG RPC: provided list of fields is empty; filter path: "
+ + filterPath));
+ }
+ final ListenableFuture<? extends DOMRpcResult> response = getConfigRunning(callback, filterPath, fields);
+ return extractData(filterPath, response);
}
+ /**
+ * Calling GET RPC with subtree filter that is specified by {@link YangInstanceIdentifier}.
+ *
+ * @param callback RPC response callback
+ * @param filterPath path to requested data
+ * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
+ */
public ListenableFuture<Optional<NormalizedNode<?, ?>>> getData(final FutureCallback<DOMRpcResult> callback,
final Optional<YangInstanceIdentifier> filterPath) {
return extractData(filterPath, get(callback, filterPath));
}
+ /**
+ * Calling GET RPC with subtree filter tha tis specified by parent {@link YangInstanceIdentifier} and list
+ * of specific fields that caller would like to read. Field paths are relative to parent path.
+ *
+ * @param callback RPC response callback
+ * @param filterPath parent path to requested data
+ * @param fields paths to specific fields that are selected under parent path
+ * @return asynchronous completion token with read {@link NormalizedNode} wrapped in {@link Optional} instance
+ */
public ListenableFuture<Optional<NormalizedNode<?, ?>>> getData(final FutureCallback<DOMRpcResult> callback,
- final Optional<YangInstanceIdentifier> path, final List<YangInstanceIdentifier> fields) {
- // FIXME: implement this method
- throw new UnsupportedOperationException();
+ final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
+ if (fields.isEmpty()) {
+ // RFC doesn't allow to build subtree filter that would expect just empty element in response
+ return Futures.immediateFailedFuture(new IllegalArgumentException(
+ "Failed to build NETCONF GET RPC: provided list of fields is empty; filter path: " + filterPath));
+ }
+ final ListenableFuture<? extends DOMRpcResult> response = get(callback, filterPath, fields);
+ return extractData(filterPath, response);
}
private ListenableFuture<Optional<NormalizedNode<?, ?>>> extractData(
return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath);
}
+ private ListenableFuture<? extends DOMRpcResult> getConfigRunning(final FutureCallback<DOMRpcResult> callback,
+ final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
+ return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath, fields);
+ }
+
public ListenableFuture<? extends DOMRpcResult> getConfigCandidate(final FutureCallback<DOMRpcResult> callback,
final Optional<YangInstanceIdentifier> filterPath) {
return getConfig(callback, NETCONF_CANDIDATE_QNAME, filterPath);
return future;
}
+ private ListenableFuture<? extends DOMRpcResult> get(final FutureCallback<DOMRpcResult> callback,
+ final Optional<YangInstanceIdentifier> filterPath, final List<YangInstanceIdentifier> fields) {
+ requireNonNull(callback);
+
+ final NormalizedNode<?, ?> rpcInput;
+ if (isFilterPresent(filterPath)) {
+ rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, transformer.toFilterStructure(
+ Collections.singletonList(FieldsFilter.of(filterPath.get(), fields))));
+ } else if (containsEmptyPath(fields)) {
+ rpcInput = GET_RPC_CONTENT;
+ } else {
+ final DataContainerChild<?, ?> subtreeFilter = getSubtreeFilterFromRootFields(fields);
+ rpcInput = NetconfMessageTransformUtil.wrap(NETCONF_GET_NODEID, subtreeFilter);
+ }
+ final ListenableFuture<? extends DOMRpcResult> response = rpc.invokeRpc(NETCONF_GET_QNAME, rpcInput);
+ Futures.addCallback(response, callback, MoreExecutors.directExecutor());
+ return response;
+ }
+
+ private static boolean containsEmptyPath(final List<YangInstanceIdentifier> fields) {
+ return fields.stream().anyMatch(YangInstanceIdentifier::isEmpty);
+ }
+
+ private DataContainerChild<?, ?> getSubtreeFilterFromRootFields(final List<YangInstanceIdentifier> fields) {
+ final Map<YangInstanceIdentifier, List<YangInstanceIdentifier>> getConfigEntries = fields.stream()
+ .map(fieldPath -> {
+ final YangInstanceIdentifier rootPath = YangInstanceIdentifier.create(
+ Iterables.limit(fieldPath.getPathArguments(), 1));
+ final YangInstanceIdentifier updatedFieldPath = YangInstanceIdentifier.create(
+ Iterables.skip(fieldPath.getPathArguments(), 1));
+ return new SimpleEntry<>(rootPath, updatedFieldPath);
+ })
+ .collect(Collectors.groupingBy(SimpleEntry::getKey,
+ Collectors.mapping(SimpleEntry::getValue, Collectors.toList())));
+ final List<FieldsFilter> fieldsFilters = getConfigEntries.keySet().stream()
+ .map(rootPath -> FieldsFilter.of(rootPath, getConfigEntries.get(rootPath)))
+ .collect(Collectors.toList());
+ return transformer.toFilterStructure(fieldsFilters);
+ }
+
private static boolean isFilterPresent(final Optional<YangInstanceIdentifier> filterPath) {
return filterPath.isPresent() && !filterPath.get().isEmpty();
}
public static final @NonNull DataContainerChild<?, ?> EMPTY_FILTER;
static {
- final Element element = XmlUtil.createElement(BLANK_DOCUMENT, NETCONF_FILTER_QNAME.getLocalName(),
- Optional.of(NETCONF_FILTER_QNAME.getNamespace().toString()));
- element.setAttributeNS(NETCONF_FILTER_QNAME.getNamespace().toString(),
- NETCONF_TYPE_QNAME.getLocalName(), SUBTREE);
-
- EMPTY_FILTER = Builders.anyXmlBuilder().withNodeIdentifier(NETCONF_FILTER_NODEID)
- .withValue(new DOMSource(element)).build();
+ final Element element = getNetconfFilterElement();
+ EMPTY_FILTER = buildFilterStructure(element);
}
+ /**
+ * Creation of the subtree filter structure using {@link YangInstanceIdentifier} path.
+ *
+ * @param identifier parent path / query
+ * @param ctx mountpoint schema context
+ * @return created DOM structure with subtree filter
+ */
public static DataContainerChild<?, ?> toFilterStructure(final YangInstanceIdentifier identifier,
final EffectiveModelContext ctx) {
- final Element element = XmlUtil.createElement(BLANK_DOCUMENT, NETCONF_FILTER_QNAME.getLocalName(),
- Optional.of(NETCONF_FILTER_QNAME.getNamespace().toString()));
- element.setAttributeNS(NETCONF_FILTER_QNAME.getNamespace().toString(), NETCONF_TYPE_QNAME.getLocalName(),
- SUBTREE);
-
+ final Element element = getNetconfFilterElement();
try {
NetconfUtil.writeFilter(identifier, new DOMResult(element), SchemaPath.ROOT, ctx);
} catch (IOException | XMLStreamException e) {
throw new IllegalStateException("Unable to serialize filter element for path " + identifier, e);
}
+ return buildFilterStructure(element);
+ }
+
+ /**
+ * Creation of the subtree filter structure using list of parent {@link YangInstanceIdentifier}
+ * and specific selection fields. Field paths are relative to parent query path.
+ *
+ * @param fieldsFilters list of: parent path and selection fields
+ * @param ctx mountpoint schema context
+ * @return created DOM structure with subtree filter
+ */
+ public static DataContainerChild<?, ?> toFilterStructure(final List<FieldsFilter> fieldsFilters,
+ final EffectiveModelContext ctx) {
+ Preconditions.checkState(!fieldsFilters.isEmpty(), "An empty list of subtree filters is not allowed");
+ final Element element = getNetconfFilterElement();
+
+ for (final FieldsFilter filter : fieldsFilters) {
+ try {
+ NetconfUtil.writeFilter(filter.path(), new DOMResult(element), SchemaPath.ROOT, ctx, filter.fields());
+ } catch (IOException | XMLStreamException e) {
+ throw new IllegalStateException(String.format(
+ "Unable to serialize filter element for path %s with fields: %s",
+ filter.path(), filter.fields()), e);
+ }
+ }
+ return buildFilterStructure(element);
+ }
+
+ private static Element getNetconfFilterElement() {
+ final Element element = XmlUtil.createElement(BLANK_DOCUMENT, NETCONF_FILTER_QNAME.getLocalName(),
+ Optional.of(NETCONF_FILTER_QNAME.getNamespace().toString()));
+ element.setAttributeNS(NETCONF_FILTER_QNAME.getNamespace().toString(), NETCONF_TYPE_QNAME.getLocalName(),
+ SUBTREE);
+ return element;
+ }
- return Builders.anyXmlBuilder().withNodeIdentifier(NETCONF_FILTER_NODEID).withValue(new DOMSource(element))
+ private static DataContainerChild<?, ?> buildFilterStructure(final Element element) {
+ return Builders.anyXmlBuilder()
+ .withNodeIdentifier(NETCONF_FILTER_NODEID)
+ .withValue(new DOMSource(element))
.build();
}
import java.io.IOException;
import java.net.URISyntaxException;
+import java.util.List;
import java.util.Optional;
import javax.xml.stream.XMLStreamException;
import org.opendaylight.netconf.api.ModifyAction;
// FIXME: propagate MountPointContext
return NetconfMessageTransformUtil.toFilterStructure(path, mountContext.getEffectiveModelContext());
}
+
+ @Override
+ public DataContainerChild<?, ?> toFilterStructure(final List<FieldsFilter> fieldsFilters) {
+ // FIXME: propagate MountPointContext
+ return NetconfMessageTransformUtil.toFilterStructure(fieldsFilters, mountContext.getEffectiveModelContext());
+ }
}
*/
package org.opendaylight.netconf.sal.connect.netconf.util;
+import java.util.List;
import java.util.Optional;
import org.opendaylight.netconf.api.ModifyAction;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
/**
* Transforms data and path to the config element structure. It means creating of parent xml structure
* specified by path and appending data to the structure. Operation is set as attribute on data element.
+ *
* @param data data
* @param dataPath path, where data will be written
* @param operation operation
/**
* Transforms path to filter structure.
+ *
* @param path path
* @return filter structure
*/
DataContainerChild<?,?> toFilterStructure(YangInstanceIdentifier path);
+ /**
+ * Transforms list of fields filters to filter structure.
+ * Field paths are relative to parent query path.
+ *
+ * @param fieldsFilters list of: parent path and selection fields
+ * @return filter structure
+ */
+ DataContainerChild<?,?> toFilterStructure(List<FieldsFilter> fieldsFilters);
+
/**
* Selects data specified by path from data node. Data must be product of get-config rpc with filter created by
- * {@link #toFilterStructure(YangInstanceIdentifier)} with same path.
+ * {@link #toFilterStructure(YangInstanceIdentifier)} or {@link #toFilterStructure(List)} )} with same path.
+ *
* @param data data
* @param path path to select
* @return selected data
.build();
}
+ @Override
+ public DataContainerChild<?, ?> toFilterStructure(final List<FieldsFilter> fieldsFilters) {
+ // todo: implementation of this feature
+ throw new UnsupportedOperationException(
+ "Creation of filter structure using fields for schemaless mountpoint is not supported");
+ }
+
private static void checkDataValidForPath(final YangInstanceIdentifier dataPath, final Element dataNode) {
//if datapath is empty, consider dataNode to be a root node
if (dataPath.isEmpty()) {
*/
package org.opendaylight.netconf.sal.connect.netconf.sal;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME;
+import com.google.common.collect.ClassToInstanceMap;
import java.net.InetSocketAddress;
import java.util.Arrays;
+import java.util.Collections;
import org.junit.AfterClass;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataBrokerExtension;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.dom.api.tx.NetconfDOMDataBrokerFieldsExtension;
+import org.opendaylight.netconf.dom.api.tx.NetconfDOMFieldsReadTransaction;
+import org.opendaylight.netconf.dom.api.tx.NetconfDOMFieldsReadWriteTransaction;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
import org.opendaylight.netconf.sal.connect.netconf.sal.tx.AbstractWriteTx;
import org.opendaylight.netconf.sal.connect.netconf.sal.tx.WriteCandidateRunningTx;
import org.opendaylight.yangtools.rcf8528.data.util.EmptyMountPointContext;
import org.opendaylight.yangtools.util.concurrent.FluentFutures;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
}
@Test
- public void testNewReadOnlyTransaction() throws Exception {
+ public void testNewReadOnlyTransaction() {
final DOMDataTreeReadTransaction tx = dataBroker.newReadOnlyTransaction();
tx.read(LogicalDatastoreType.OPERATIONAL, null);
verify(rpcService).invokeRpc(eq(NETCONF_GET_QNAME), any(ContainerNode.class));
}
@Test
- public void testNewReadWriteTransaction() throws Exception {
+ public void testNewReadWriteTransaction() {
final DOMDataTreeReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
tx.read(LogicalDatastoreType.OPERATIONAL, null);
verify(rpcService).invokeRpc(eq(NETCONF_GET_QNAME), any(ContainerNode.class));
}
@Test
- public void testWritableRunningCandidateWriteTransaction() throws Exception {
+ public void testWritableRunningCandidateWriteTransaction() {
testWriteTransaction(
WriteCandidateRunningTx.class, NetconfMessageTransformUtil.NETCONF_RUNNING_WRITABLE_URI.toString(),
NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString());
}
@Test
- public void testCandidateWriteTransaction() throws Exception {
+ public void testCandidateWriteTransaction() {
testWriteTransaction(WriteCandidateTx.class, NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString());
}
@Test
- public void testRunningWriteTransaction() throws Exception {
+ public void testRunningWriteTransaction() {
testWriteTransaction(WriteRunningTx.class, NetconfMessageTransformUtil.NETCONF_RUNNING_WRITABLE_URI.toString());
}
+ @Test
+ public void testDOMFieldsExtensions() {
+ final ClassToInstanceMap<DOMDataBrokerExtension> extensions = dataBroker.getExtensions();
+ final NetconfDOMDataBrokerFieldsExtension fieldsExtension = extensions.getInstance(
+ NetconfDOMDataBrokerFieldsExtension.class);
+ assertNotNull(fieldsExtension);
+
+ // read-only transaction
+ final NetconfDOMFieldsReadTransaction roTx = fieldsExtension.newReadOnlyTransaction();
+ roTx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(),
+ Collections.singletonList(YangInstanceIdentifier.empty()));
+ verify(rpcService).invokeRpc(Mockito.eq(NETCONF_GET_CONFIG_QNAME), any(ContainerNode.class));
+
+ // read-write transaction
+ final NetconfDOMFieldsReadWriteTransaction rwTx = fieldsExtension.newReadWriteTransaction();
+ rwTx.read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.empty(),
+ Collections.singletonList(YangInstanceIdentifier.empty()));
+ verify(rpcService).invokeRpc(Mockito.eq(NETCONF_GET_QNAME), any(ContainerNode.class));
+ }
+
private void testWriteTransaction(final Class<? extends AbstractWriteTx> transaction,
final String... capabilities) {
NetconfDeviceDataBroker db = getDataBroker(capabilities);
- Assert.assertEquals(transaction, db.newWriteOnlyTransaction().getClass());
+ assertEquals(transaction, db.newWriteOnlyTransaction().getClass());
}
private NetconfDeviceDataBroker getDataBroker(final String... caps) {
--- /dev/null
+/*
+ * Copyright © 2020 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.sal.connect.netconf.sal.tx;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME;
+import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
+import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
+import org.opendaylight.yangtools.util.concurrent.FluentFutures;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class FieldsAwareReadOnlyTxTest {
+ @Mock
+ private DOMRpcService rpc;
+ @Mock
+ private NormalizedNode<?, ?> mockedNode;
+
+ @Test
+ public void testReadWithFields() {
+ doReturn(FluentFutures.immediateFluentFuture(new DefaultDOMRpcResult(mockedNode))).when(rpc)
+ .invokeRpc(any(QName.class), any(ContainerNode.class));
+
+ final NetconfBaseOps netconfOps = new NetconfBaseOps(rpc, mock(MountPointContext.class));
+ try (FieldsAwareReadOnlyTx readOnlyTx = new FieldsAwareReadOnlyTx(netconfOps,
+ new RemoteDeviceId("a", new InetSocketAddress("localhost", 196)))) {
+
+ readOnlyTx.read(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(),
+ List.of(YangInstanceIdentifier.empty()));
+ verify(rpc).invokeRpc(eq(NETCONF_GET_CONFIG_QNAME), any(ContainerNode.class));
+
+ readOnlyTx.read(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.empty());
+ verify(rpc).invokeRpc(eq(NETCONF_GET_QNAME), any(ContainerNode.class));
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright © 2020 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.sal.connect.netconf.sal.tx;
+
+import static org.mockito.Mockito.verify;
+
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
+import org.opendaylight.netconf.dom.api.tx.NetconfDOMFieldsReadTransaction;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class FieldsAwareReadWriteTxTest {
+ @Mock
+ private NetconfDOMFieldsReadTransaction delegateReadTx;
+ @Mock
+ private DOMDataTreeWriteTransaction delegateWriteTx;
+
+ @Test
+ public void testReadWithFields() {
+ final FieldsAwareReadWriteTx tx = new FieldsAwareReadWriteTx(delegateReadTx, delegateWriteTx);
+ tx.read(LogicalDatastoreType.CONFIGURATION, TxTestUtils.getContainerId(),
+ List.of(YangInstanceIdentifier.empty()));
+ verify(delegateReadTx).read(LogicalDatastoreType.CONFIGURATION, TxTestUtils.getContainerId(),
+ List.of(YangInstanceIdentifier.empty()));
+ }
+}
\ No newline at end of file
private DOMDataTreeReadTransaction delegateReadTx;
@Mock
private DOMDataTreeWriteTransaction delegateWriteTx;
- private ReadWriteTx tx;
+ private ReadWriteTx<?> tx;
@Before
public void setUp() throws Exception {
- tx = new ReadWriteTx(delegateReadTx, delegateWriteTx);
+ tx = new ReadWriteTx<>(delegateReadTx, delegateWriteTx);
}
@Test
@Test
public void getIdentifier() throws Exception {
- final ReadWriteTx tx2 = new ReadWriteTx(null, null);
+ final ReadWriteTx<?> tx2 = new ReadWriteTx<>(null, null);
assertNotEquals(tx.getIdentifier(), tx2.getIdentifier());
}
}
import org.opendaylight.netconf.api.xml.XmlUtil;
import org.opendaylight.netconf.sal.connect.netconf.AbstractBaseSchemasTest;
import org.opendaylight.netconf.sal.connect.netconf.schema.NetconfRemoteSchemaYangSourceProvider;
+import org.opendaylight.netconf.sal.connect.netconf.util.FieldsFilter;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
import org.opendaylight.netconf.util.NetconfUtil;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.IetfNetconfService;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Capabilities;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Datastores;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Sessions;
+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.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;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
assertTrue(actionResult.getOutput().isEmpty());
}
+ @Test
+ public void getTwoNonOverlappingFieldsTest() throws IOException, SAXException {
+ // preparation of the fields
+ final YangInstanceIdentifier parentYiid = YangInstanceIdentifier.create(toId(NetconfState.QNAME));
+ final YangInstanceIdentifier netconfStartTimeField = YangInstanceIdentifier.create(toId(Statistics.QNAME),
+ toId(QName.create(Statistics.QNAME, "netconf-start-time")));
+ 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 NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
+ NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
+
+ // testing
+ assertSimilarXml(netconfMessage, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + "<get>\n"
+ + "<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">\n"
+ + "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">\n"
+ + "<statistics>\n"
+ + "<netconf-start-time/>\n"
+ + "</statistics>\n"
+ + "<datastores/>\n"
+ + "</netconf-state>\n"
+ + "</filter>\n"
+ + "</get>\n"
+ + "</rpc>");
+ }
+
+ @Test
+ public void getOverlappingFieldsTest() throws IOException, SAXException {
+ // preparation of the fields
+ final YangInstanceIdentifier parentYiid = YangInstanceIdentifier.create(toId(NetconfState.QNAME));
+ final YangInstanceIdentifier capabilitiesField = YangInstanceIdentifier.create(toId(Capabilities.QNAME));
+ final YangInstanceIdentifier capabilityField = YangInstanceIdentifier.create(toId(Capabilities.QNAME),
+ toId(QName.create(Capabilities.QNAME, "capability").intern()));
+ final YangInstanceIdentifier datastoreField = YangInstanceIdentifier.create(toId(Datastores.QNAME));
+ final YangInstanceIdentifier locksFields = YangInstanceIdentifier.create(toId(Datastores.QNAME),
+ 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 NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
+ NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
+
+ // testing
+ assertSimilarXml(netconfMessage, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + "<get>\n"
+ + "<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">\n"
+ + "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">\n"
+ + "<capabilities/>\n"
+ + "<datastores/>\n"
+ + "</netconf-state>\n"
+ + "</filter>\n"
+ + "</get>\n"
+ + "</rpc>");
+ }
+
+ @Test
+ public void getOverlappingFieldsWithEmptyFieldTest() throws IOException, SAXException {
+ // preparation of the fields
+ final YangInstanceIdentifier parentYiid = YangInstanceIdentifier.create(toId(NetconfState.QNAME));
+ 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 NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
+ NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
+
+ // testing
+ assertSimilarXml(netconfMessage, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + "<get>\n"
+ + "<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">\n"
+ + "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\"/>\n"
+ + "</filter>\n"
+ + "</get>\n"
+ + "</rpc>");
+ }
+
+ @Test
+ public void getSpecificFieldsUnderListTest() throws IOException, SAXException {
+ // preparation of the fields
+ final YangInstanceIdentifier parentYiid = YangInstanceIdentifier.create(toId(NetconfState.QNAME),
+ toId(Schemas.QNAME), toId(Schema.QNAME), NodeIdentifierWithPredicates.of(Schema.QNAME));
+ final YangInstanceIdentifier versionField = YangInstanceIdentifier.create(
+ toId(QName.create(Schema.QNAME, "version").intern()));
+ final YangInstanceIdentifier identifierField = YangInstanceIdentifier.create(
+ toId(QName.create(Schema.QNAME, "identifier").intern()));
+
+ // building filter structure and NETCONF message
+ final DataContainerChild<?, ?> filterStructure = toFilterStructure(Collections.singletonList(FieldsFilter.of(
+ parentYiid, List.of(versionField, identifierField))), SCHEMA);
+ final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
+ NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
+
+ // testing
+ assertSimilarXml(netconfMessage, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + "<get>\n"
+ + "<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">\n"
+ + "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">\n"
+ + "<schemas>\n"
+ + "<schema>\n"
+ + "<version/>\n"
+ + "<identifier/>\n"
+ + "</schema>\n"
+ + "</schemas>\n"
+ + "</netconf-state>\n"
+ + "</filter>\n"
+ + "</get>\n"
+ + "</rpc>");
+ }
+
+ @Test
+ public void getWholeListsUsingFieldsTest() throws IOException, SAXException {
+ // preparation of the fields
+ final YangInstanceIdentifier parentYiid = YangInstanceIdentifier.create(toId(NetconfState.QNAME));
+ final YangInstanceIdentifier datastoreListField = YangInstanceIdentifier.create(toId(Datastores.QNAME),
+ toId(Datastore.QNAME), NodeIdentifierWithPredicates.of(Datastore.QNAME));
+ final YangInstanceIdentifier sessionListField = YangInstanceIdentifier.create(toId(Sessions.QNAME),
+ 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 NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
+ NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
+
+ // testing
+ assertSimilarXml(netconfMessage, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + "<get>\n"
+ + "<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">\n"
+ + "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">\n"
+ + "<datastores>\n"
+ + "<datastore/>\n"
+ + "</datastores>\n"
+ + "<sessions>\n"
+ + "<session/>\n"
+ + "</sessions>\n"
+ + "</netconf-state>\n"
+ + "</filter>\n"
+ + "</get>\n"
+ + "</rpc>");
+ }
+
+ @Test
+ public void getSpecificListEntriesWithSpecificFieldsTest() throws IOException, SAXException {
+ // preparation of the fields
+ final YangInstanceIdentifier parentYiid = YangInstanceIdentifier.create(toId(NetconfState.QNAME),
+ toId(Sessions.QNAME));
+ final QName sessionId = QName.create(Session.QNAME, "session-id").intern();
+ final YangInstanceIdentifier session1Field = YangInstanceIdentifier.create(toId(Session.QNAME),
+ NodeIdentifierWithPredicates.of(Session.QNAME, sessionId, 1));
+ final YangInstanceIdentifier session2TransportField = YangInstanceIdentifier.create(toId(Session.QNAME),
+ NodeIdentifierWithPredicates.of(Session.QNAME, sessionId, 2),
+ 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 NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(NETCONF_GET_QNAME,
+ NetconfMessageTransformUtil.wrap(toId(NETCONF_GET_QNAME), filterStructure));
+
+ // testing
+ assertSimilarXml(netconfMessage, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+ + "<get>\n"
+ + "<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">\n"
+ + "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">\n"
+ + "<sessions>\n"
+ + "<session>\n"
+ + "<session-id>1</session-id>\n"
+ + "</session>\n"
+ + "<session>\n"
+ + "<session-id>2</session-id>\n"
+ + "<transport/>\n"
+ + "</session>\n"
+ + "</sessions>\n"
+ + "</netconf-state>\n"
+ + "</filter>\n"
+ + "</get>\n"
+ + "</rpc>");
+ }
+
private static void checkAction(final QName actionQname, final Node action , final String inputLocalName,
final String inputNodeName, final String inputValue) {
checkNode(action, actionQname.getLocalName(), actionQname.getLocalName(),
Node messageId = baseRpc.getAttributes().getNamedItem("message-id");
assertNotNull(messageId);
assertTrue(messageId.getNodeValue().contains("m-"));
-
Node childAction = baseRpc.getFirstChild();
+
checkNode(childAction, "action", "action", NetconfMessageTransformUtil.NETCONF_ACTION_NAMESPACE.toString());
return childAction;
}
*/
package org.opendaylight.netconf.sal.connect.netconf.util;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
+import java.net.URI;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.ExecutionException;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.XMLUnit;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.opendaylight.netconf.util.NetconfUtil;
import org.opendaylight.yangtools.rcf8528.data.util.EmptyMountPointContext;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.Revision;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class NetconfBaseOpsTest extends AbstractTestModelTest {
- private static final QName CONTAINER_Q_NAME = QName.create("test:namespace", "2013-07-22", "c");
+ private static final QNameModule TEST_MODULE = QNameModule.create(
+ URI.create("test:namespace"), Revision.of("2013-07-22"));
+
+ private static final QName CONTAINER_C_QNAME = QName.create(TEST_MODULE, "c");
+ private static final NodeIdentifier CONTAINER_C_NID = NodeIdentifier.create(CONTAINER_C_QNAME);
+ private static final QName LEAF_A_QNAME = QName.create(TEST_MODULE, "a");
+ private static final NodeIdentifier LEAF_A_NID = NodeIdentifier.create(LEAF_A_QNAME);
+ private static final QName LEAF_B_QNAME = QName.create(TEST_MODULE, "b");
+ private static final NodeIdentifier LEAF_B_NID = NodeIdentifier.create(LEAF_B_QNAME);
+ private static final QName CONTAINER_D_QNAME = QName.create(TEST_MODULE, "d");
+ private static final NodeIdentifier CONTAINER_D_NID = NodeIdentifier.create(CONTAINER_D_QNAME);
+ private static final QName LEAF_X_QNAME = QName.create(TEST_MODULE, "x");
+ private static final NodeIdentifier LEAF_X_NID = NodeIdentifier.create(LEAF_X_QNAME);
+
+ private static final QName CONTAINER_E_QNAME = QName.create(TEST_MODULE, "e");
+ private static final NodeIdentifier CONTAINER_E_NID = NodeIdentifier.create(CONTAINER_E_QNAME);
+ private static final QName LEAF_Z_QNAME = QName.create(TEST_MODULE, "z");
+ private static final NodeIdentifier LEAF_Z_NID = NodeIdentifier.create(LEAF_Z_QNAME);
static {
XMLUnit.setIgnoreWhitespace(true);
public void testGetConfigRunningData() throws Exception {
final Optional<NormalizedNode<?, ?>> dataOpt =
baseOps.getConfigRunningData(callback, Optional.of(YangInstanceIdentifier.empty())).get();
- Assert.assertTrue(dataOpt.isPresent());
- Assert.assertEquals(NetconfUtil.NETCONF_DATA_QNAME, dataOpt.get().getNodeType());
+ assertTrue(dataOpt.isPresent());
+ assertEquals(NetconfUtil.NETCONF_DATA_QNAME, dataOpt.get().getNodeType());
}
@Test
public void testGetData() throws Exception {
final Optional<NormalizedNode<?, ?>> dataOpt =
baseOps.getData(callback, Optional.of(YangInstanceIdentifier.empty())).get();
- Assert.assertTrue(dataOpt.isPresent());
- Assert.assertEquals(NetconfUtil.NETCONF_DATA_QNAME, dataOpt.get().getNodeType());
+ assertTrue(dataOpt.isPresent());
+ assertEquals(NetconfUtil.NETCONF_DATA_QNAME, dataOpt.get().getNodeType());
}
@Test
@Test
public void testGetConfigCandidateWithFilter() throws Exception {
final YangInstanceIdentifier id = YangInstanceIdentifier.builder()
- .node(CONTAINER_Q_NAME)
+ .node(CONTAINER_C_QNAME)
.build();
baseOps.getConfigCandidate(callback, Optional.of(id));
verifyMessageSent("getConfig_candidate-filter", NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME);
@Test
public void testEditConfigCandidate() throws Exception {
- final QName leafQName = QName.create(CONTAINER_Q_NAME, "a");
final LeafNode<Object> leaf = Builders.leafBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(leafQName))
+ .withNodeIdentifier(LEAF_A_NID)
.withValue("leaf-value")
.build();
final YangInstanceIdentifier leafId = YangInstanceIdentifier.builder()
- .node(CONTAINER_Q_NAME)
- .node(leafQName)
+ .node(CONTAINER_C_QNAME)
+ .node(LEAF_A_NID)
.build();
final DataContainerChild<?, ?> structure = baseOps.createEditConfigStrcture(Optional.of(leaf),
Optional.of(ModifyAction.REPLACE), leafId);
@Test
public void testEditConfigRunning() throws Exception {
- final QName containerQName = QName.create("test:namespace", "2013-07-22", "c");
- final QName leafQName = QName.create(containerQName, "a");
final LeafNode<Object> leaf = Builders.leafBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(leafQName))
+ .withNodeIdentifier(LEAF_A_NID)
.withValue("leaf-value")
.build();
final YangInstanceIdentifier leafId = YangInstanceIdentifier.builder()
- .node(containerQName)
- .node(leafQName)
+ .node(CONTAINER_C_NID)
+ .node(LEAF_A_NID)
.build();
final DataContainerChild<?, ?> structure = baseOps.createEditConfigStrcture(Optional.of(leaf),
Optional.of(ModifyAction.REPLACE), leafId);
verifyMessageSent("edit-config-test-module-running", NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME);
}
+ @Test
+ public void testGetWithFields() throws ExecutionException, InterruptedException {
+ final YangInstanceIdentifier path = YangInstanceIdentifier.create(CONTAINER_C_NID);
+ final YangInstanceIdentifier leafAField = YangInstanceIdentifier.create(LEAF_A_NID);
+ final YangInstanceIdentifier leafBField = YangInstanceIdentifier.create(LEAF_B_NID);
+
+ baseOps.getData(callback, Optional.of(path), List.of(leafAField, leafBField)).get();
+ verify(listener).sendRequest(msg("/netconfMessages/get-fields-request.xml"),
+ eq(NetconfMessageTransformUtil.NETCONF_GET_QNAME));
+ }
+
+ @Test
+ public void testGetConfigWithFields() throws ExecutionException, InterruptedException {
+ final YangInstanceIdentifier path = YangInstanceIdentifier.create(CONTAINER_C_NID);
+ final YangInstanceIdentifier leafAField = YangInstanceIdentifier.create(LEAF_A_NID);
+ final YangInstanceIdentifier leafBField = YangInstanceIdentifier.create(LEAF_B_NID);
+
+ baseOps.getConfigRunningData(callback, Optional.of(path), List.of(leafAField, leafBField)).get();
+ verify(listener).sendRequest(msg("/netconfMessages/get-config-fields-request.xml"),
+ eq(NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME));
+ }
+
+ @Test
+ public void testGetDataWithoutFields() {
+ assertThrows(ExecutionException.class, () -> baseOps.getData(callback,
+ Optional.of(YangInstanceIdentifier.empty()), Collections.emptyList()).get());
+ }
+
+ @Test
+ public void getConfigRunningDataWithoutFields() {
+ assertThrows(ExecutionException.class, () -> baseOps.getConfigRunningData(callback,
+ Optional.of(YangInstanceIdentifier.empty()), Collections.emptyList()).get());
+ }
+
+ @Test
+ public void testGetWithFieldsAndEmptyParentPath() throws ExecutionException, InterruptedException {
+ final YangInstanceIdentifier leafAField = YangInstanceIdentifier.create(CONTAINER_C_NID, LEAF_A_NID);
+ final YangInstanceIdentifier leafXField = YangInstanceIdentifier.create(
+ CONTAINER_C_NID, CONTAINER_D_NID, LEAF_X_NID);
+ final YangInstanceIdentifier leafZField = YangInstanceIdentifier.create(CONTAINER_E_NID, LEAF_Z_NID);
+
+ baseOps.getData(callback, Optional.of(YangInstanceIdentifier.empty()),
+ List.of(leafAField, leafXField, leafZField)).get();
+ verify(listener).sendRequest(msg("/netconfMessages/get-with-multiple-subtrees.xml"),
+ eq(NetconfMessageTransformUtil.NETCONF_GET_QNAME));
+ }
+
+ @Test
+ public void testGetConfigWithFieldsAndEmptyParentPath() throws ExecutionException, InterruptedException {
+ final YangInstanceIdentifier leafAField = YangInstanceIdentifier.create(CONTAINER_C_NID, LEAF_A_NID);
+ final YangInstanceIdentifier leafXField = YangInstanceIdentifier.create(
+ CONTAINER_C_NID, CONTAINER_D_NID, LEAF_X_NID);
+ final YangInstanceIdentifier leafZField = YangInstanceIdentifier.create(CONTAINER_E_NID, LEAF_Z_NID);
+
+ baseOps.getConfigRunningData(callback, Optional.of(YangInstanceIdentifier.empty()),
+ List.of(leafAField, leafXField, leafZField)).get();
+ verify(listener).sendRequest(msg("/netconfMessages/get-config-with-multiple-subtrees.xml"),
+ eq(NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME));
+ }
+
+ @Test
+ public void testGetWithRootFieldsAndEmptyParentPath() throws ExecutionException, InterruptedException {
+ final YangInstanceIdentifier contCField = YangInstanceIdentifier.create(CONTAINER_C_NID);
+ final YangInstanceIdentifier contDField = YangInstanceIdentifier.create(CONTAINER_E_NID);
+
+ baseOps.getData(callback, Optional.of(YangInstanceIdentifier.empty()), List.of(contCField, contDField)).get();
+ verify(listener).sendRequest(msg("/netconfMessages/get-with-multiple-root-subtrees.xml"),
+ eq(NetconfMessageTransformUtil.NETCONF_GET_QNAME));
+ }
+
private void verifyMessageSent(final String fileName, final QName name) {
final String path = "/netconfMessages/" + fileName + ".xml";
verify(listener).sendRequest(msg(path), eq(name));
leaf a {
type string;
}
+
+ leaf b {
+ type string;
+ }
+
+ container d {
+ leaf x {
+ type boolean;
+ }
+ }
}
+ container e {
+ leaf z {
+ type uint8;
+ }
+ }
}