import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.patch.PatchContext;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierSerializer;
import org.opendaylight.restconf.server.api.DataPatchPath;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
return urlPath;
}
+ // FIXME: NETCONF-1157: these two operations should really be a single ApiPathNormalizer step -- but for that
+ // we need to switch to ApiPath forms
final var databind = path.databind();
final String targetUrl;
if (urlPath.isEmpty()) {
}
try {
- return InstanceIdentifierContext.ofApiPath(ApiPath.parse(targetUrl), databind, null)
- .getInstanceIdentifier();
+ return new ApiPathNormalizer(databind).normalizeDataPath(ApiPath.parse(targetUrl)).instance();
} catch (ParseException | RestconfDocumentedException e) {
throw new RestconfDocumentedException("Failed to parse target " + target,
ErrorType.RPC, ErrorTag.MALFORMED_MESSAGE, e);
+++ /dev/null
-/*
- * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.restconf.nb.rfc8040.legacy;
-
-import static com.google.common.base.Verify.verify;
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.mdsal.dom.api.DOMMountPoint;
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.mdsal.dom.api.DOMSchemaService;
-import org.opendaylight.restconf.api.ApiPath;
-import org.opendaylight.restconf.api.ApiPath.Step;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.server.api.DatabindContext;
-import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
-import org.opendaylight.yangtools.yang.common.ErrorTag;
-import org.opendaylight.yangtools.yang.common.ErrorType;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.model.api.SchemaNode;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
-
-public abstract class InstanceIdentifierContext {
- private static final class Root extends InstanceIdentifierContext {
- Root(final DatabindContext databind, final DOMMountPoint mountPoint) {
- super(databind, databind.modelContext(), mountPoint);
- }
-
- @Override
- public YangInstanceIdentifier getInstanceIdentifier() {
- return YangInstanceIdentifier.of();
- }
-
- @Override
- public Inference inference() {
- return SchemaInferenceStack.of(databind().modelContext()).toInference();
- }
- }
-
- private static final class DataPath extends InstanceIdentifierContext {
- private final @NonNull YangInstanceIdentifier path;
- private final @NonNull SchemaInferenceStack stack;
-
- private DataPath(final DatabindContext databind, final SchemaNode schemaNode, final DOMMountPoint mountPoint,
- final SchemaInferenceStack stack, final YangInstanceIdentifier path) {
- super(databind, schemaNode, mountPoint);
- this.stack = requireNonNull(stack);
- this.path = requireNonNull(path);
- }
-
- static @NonNull DataPath of(final DatabindContext databind, final YangInstanceIdentifier path,
- final DOMMountPoint mountPoint) {
- final var nodeAndStack = databind.schemaTree().enterPath(path).orElseThrow();
- return new DataPath(databind, nodeAndStack.node().dataSchemaNode(), mountPoint, nodeAndStack.stack(), path);
- }
-
- @Override
- public YangInstanceIdentifier getInstanceIdentifier() {
- return path;
- }
-
- @Override
- public Inference inference() {
- return stack.toInference();
- }
- }
-
- private static final class WithoutDataPath extends InstanceIdentifierContext {
- private final @NonNull SchemaInferenceStack stack;
-
- private WithoutDataPath(final DatabindContext databind, final SchemaNode schemaNode,
- final DOMMountPoint mountPoint, final SchemaInferenceStack stack) {
- super(databind, schemaNode, mountPoint);
- this.stack = requireNonNull(stack);
- }
-
- @Override
- public Inference inference() {
- return stack.toInference();
- }
-
- @Override
- public @Nullable YangInstanceIdentifier getInstanceIdentifier() {
- return null;
- }
- }
-
- private final @NonNull DatabindContext databind;
- private final @NonNull SchemaNode schemaNode;
- private final @Nullable DOMMountPoint mountPoint;
-
- InstanceIdentifierContext(final DatabindContext databind, final SchemaNode schemaNode,
- final DOMMountPoint mountPoint) {
- this.databind = requireNonNull(databind);
- this.schemaNode = requireNonNull(schemaNode);
- this.mountPoint = mountPoint;
- }
-
- // FIXME: NETCONF-773: this recursion should really live in MdsalRestconfServer
- public static @NonNull InstanceIdentifierContext ofApiPath(final ApiPath path, final DatabindContext databind,
- final DOMMountPointService mountPointService) {
- final var steps = path.steps();
- final var limit = steps.size() - 1;
-
- var prefix = 0;
- DOMMountPoint currentMountPoint = null;
- var currentDatabind = databind;
- while (prefix <= limit) {
- final var mount = indexOfMount(steps, prefix, limit);
- if (mount == -1) {
- break;
- }
-
- final var mountService = currentMountPoint == null ? mountPointService
- : currentMountPoint.getService(DOMMountPointService.class).orElse(null);
- if (mountService == null) {
- throw new RestconfDocumentedException("Mount point service is not available",
- ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
- }
-
- final var mountPath = new ApiPathNormalizer(databind).normalizePath(path.subPath(prefix, mount)).path;
- final var userPath = path.subPath(0, mount);
- final var nextMountPoint = mountService.getMountPoint(mountPath)
- .orElseThrow(() -> new RestconfDocumentedException("Mount point '" + userPath + "' does not exist",
- ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
- final var nextModelContext = nextMountPoint.getService(DOMSchemaService.class)
- .orElseThrow(() -> new RestconfDocumentedException(
- "Mount point '" + userPath + "' does not expose DOMSchemaService",
- ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT))
- .getGlobalContext();
- if (nextModelContext == null) {
- throw new RestconfDocumentedException("Mount point '" + userPath + "' does not have any models",
- ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT);
- }
-
- prefix = mount + 1;
- currentDatabind = DatabindContext.ofModel(nextModelContext);
- currentMountPoint = nextMountPoint;
- }
-
- final var result = new ApiPathNormalizer(currentDatabind).normalizePath(path.subPath(prefix));
- return InstanceIdentifierContext.ofPath(currentDatabind, result.stack, result.node, result.path,
- currentMountPoint);
- }
-
- private static int indexOfMount(final ImmutableList<Step> steps, final int fromIndex, final int limit) {
- for (int i = fromIndex; i <= limit; ++ i) {
- final var step = steps.get(i);
- if ("yang-ext".equals(step.module()) && "mount".equals(step.identifier().getLocalName())) {
- return i;
- }
- }
- return -1;
- }
-
- public static @NonNull InstanceIdentifierContext ofLocalRoot(final DatabindContext databind) {
- return new Root(databind, null);
- }
-
- @VisibleForTesting
- public static @NonNull InstanceIdentifierContext ofLocalPath(final DatabindContext databind,
- final YangInstanceIdentifier path) {
- return DataPath.of(databind, path, null);
- }
-
- // Invocations of various identifier-less details
- public static @NonNull InstanceIdentifierContext ofStack(final DatabindContext databind,
- final SchemaInferenceStack stack) {
- return ofStack(databind, stack, null);
- }
-
- // Invocations of various identifier-less details, potentially having a mount point
- public static @NonNull InstanceIdentifierContext ofStack(final DatabindContext databind,
- final SchemaInferenceStack stack, final @Nullable DOMMountPoint mountPoint) {
- final SchemaNode schemaNode;
- if (!stack.isEmpty()) {
- final var stmt = stack.currentStatement();
- verify(stmt instanceof SchemaNode, "Unexpected statement %s", stmt);
- schemaNode = (SchemaNode) stmt;
- } else {
- schemaNode = stack.getEffectiveModelContext();
- }
-
- return new WithoutDataPath(databind, schemaNode, mountPoint, stack);
- }
-
- public static @NonNull InstanceIdentifierContext ofPath(final DatabindContext databind,
- final SchemaInferenceStack stack, final SchemaNode schemaNode, final YangInstanceIdentifier path,
- final @Nullable DOMMountPoint mountPoint) {
- return new DataPath(databind, schemaNode, mountPoint, stack, path);
- }
-
- public final @NonNull DatabindContext databind() {
- return databind;
- }
-
- public final @NonNull SchemaNode getSchemaNode() {
- return schemaNode;
- }
-
- public final @Nullable DOMMountPoint getMountPoint() {
- return mountPoint;
- }
-
- public abstract @NonNull Inference inference();
-
- public abstract @Nullable YangInstanceIdentifier getInstanceIdentifier();
-}
}
public NormalizedNodePayload(final Inference inference, final NormalizedNode data) {
- this(inference, data, QueryParameters.empty());
+ this(inference, data, QueryParameters.EMPTY);
}
}
*/
package org.opendaylight.restconf.nb.rfc8040.legacy;
-import static java.util.Objects.requireNonNull;
-
import com.google.common.annotations.Beta;
import java.util.List;
import java.util.Set;
import org.opendaylight.restconf.api.query.PrettyPrintParam;
import org.opendaylight.restconf.server.api.DataGetParams;
import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
/**
* This holds various options acquired from a requests's query part. This class needs to be further split up to make
*/
@Beta
// FIXME: this probably needs to be renamed back to WriterParams, or somesuch
-public final class QueryParameters {
- private static final @NonNull QueryParameters EMPTY = of(DataGetParams.EMPTY);
-
- private final @NonNull DataGetParams params;
- private final List<YangInstanceIdentifier> fieldPaths;
- private final List<Set<QName>> fields;
-
- private QueryParameters(final DataGetParams params, final List<Set<QName>> fields,
- final List<YangInstanceIdentifier> fieldPaths) {
- this.params = requireNonNull(params);
- this.fields = fields;
- this.fieldPaths = fieldPaths;
- }
-
- public static @NonNull QueryParameters empty() {
- return EMPTY;
- }
+public record QueryParameters(
+ @Nullable DepthParam depth,
+ @Nullable PrettyPrintParam prettyPrint,
+ @Nullable List<Set<QName>> fields) {
+ public static final @NonNull QueryParameters EMPTY = new QueryParameters(null, null, null);
public static @NonNull QueryParameters of(final DataGetParams params) {
- return new QueryParameters(params, null, null);
- }
-
- public static @NonNull QueryParameters ofFields(final DataGetParams params, final List<Set<QName>> fields) {
- return new QueryParameters(params, fields, null);
- }
-
- public static @NonNull QueryParameters ofFieldPaths(final DataGetParams params,
- final List<YangInstanceIdentifier> fieldPaths) {
- return new QueryParameters(params, null, fieldPaths);
- }
-
- public @Nullable DepthParam depth() {
- return params.depth();
- }
-
- public @Nullable PrettyPrintParam prettyPrint() {
- return params.prettyPrint();
- }
-
- public @Nullable List<Set<QName>> fields() {
- return fields;
+ final var depth = params.depth();
+ final var prettyPrint = params.prettyPrint();
+ return depth == null && prettyPrint == null ? EMPTY : new QueryParameters(depth, prettyPrint, null);
}
- public @Nullable List<YangInstanceIdentifier> fieldPaths() {
- return fieldPaths;
+ public static @NonNull QueryParameters of(final DataGetParams params, final List<Set<QName>> fields) {
+ return fields == null ? of(params) : new QueryParameters(params.depth(), params.prettyPrint(), fields);
}
}
import com.google.common.collect.ImmutableMap;
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.List;
import java.util.Optional;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
+import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
+import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
+import org.opendaylight.restconf.server.api.DataGetParams;
import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
import org.opendaylight.restconf.server.spi.RpcImplementation;
import org.opendaylight.yangtools.yang.common.Empty;
import org.opendaylight.yangtools.yang.common.ErrorTag;
private final DOMDataBroker dataBroker;
public MdsalRestconfStrategy(final DatabindContext databind, final DOMDataBroker dataBroker,
- final @Nullable DOMRpcService rpcService, final @Nullable DOMYangTextSourceProvider sourceProvider,
+ final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
+ final @Nullable DOMYangTextSourceProvider sourceProvider,
+ final @Nullable DOMMountPointService mountPointService,
final ImmutableMap<QName, RpcImplementation> localRpcs) {
- super(databind, localRpcs, rpcService, sourceProvider);
+ super(databind, localRpcs, rpcService, actionService, sourceProvider, mountPointService);
this.dataBroker = requireNonNull(dataBroker);
}
public MdsalRestconfStrategy(final DatabindContext databind, final DOMDataBroker dataBroker,
- final @Nullable DOMRpcService rpcService, final @Nullable DOMYangTextSourceProvider sourceProvider) {
- this(databind, dataBroker, rpcService, sourceProvider, ImmutableMap.of());
+ final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
+ final @Nullable DOMYangTextSourceProvider sourceProvider,
+ final @Nullable DOMMountPointService mountPointService) {
+ this(databind, dataBroker, rpcService, actionService, sourceProvider, mountPointService, ImmutableMap.of());
}
@Override
}, MoreExecutors.directExecutor());
}
+ @Override
+ RestconfFuture<NormalizedNodePayload> dataGET(final DataPath path, final DataGetParams params) {
+ final var inference = path.inference();
+ final var fields = params.fields();
+ final var translatedFields = fields == null ? null
+ : WriterFieldsTranslator.translate(inference.getEffectiveModelContext(), path.schema(), fields);
+ return completeDataGET(inference, QueryParameters.of(params, translatedFields),
+ readData(params.content(), path.instance(), params.withDefaults()));
+ }
+
@Override
ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store,
final YangInstanceIdentifier path) {
}
}
- @Override
- ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store, final YangInstanceIdentifier path,
- final List<YangInstanceIdentifier> fields) {
- return Futures.immediateFailedFuture(new UnsupportedOperationException(
- "Reading of selected subtrees is currently not supported in: " + MdsalRestconfStrategy.class));
- }
-
@Override
ListenableFuture<Boolean> exists(final YangInstanceIdentifier path) {
try (var tx = dataBroker.newReadOnlyTransaction()) {
import com.google.common.util.concurrent.SettableFuture;
import java.util.List;
import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
+import org.opendaylight.restconf.api.query.ContentParam;
+import org.opendaylight.restconf.api.query.WithDefaultsParam;
+import org.opendaylight.restconf.common.errors.RestconfFuture;
import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
+import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
+import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
+import org.opendaylight.restconf.server.api.DataGetParams;
import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
import org.opendaylight.yangtools.yang.common.Empty;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
private final NetconfDataTreeService netconfService;
public NetconfRestconfStrategy(final DatabindContext databind, final NetconfDataTreeService netconfService,
- final @Nullable DOMRpcService rpcService, final @Nullable DOMYangTextSourceProvider sourceProvider) {
- super(databind, ImmutableMap.of(), rpcService, sourceProvider);
+ final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
+ final @Nullable DOMYangTextSourceProvider sourceProvider,
+ final @Nullable DOMMountPointService mountPointService) {
+ super(databind, ImmutableMap.of(), rpcService, actionService, sourceProvider, mountPointService);
this.netconfService = requireNonNull(netconfService);
}
+ @Override
+ RestconfTransaction prepareWriteExecution() {
+ return new NetconfRestconfTransaction(modelContext(), netconfService);
+ }
+
@Override
void delete(final SettableRestconfFuture<Empty> future, final YangInstanceIdentifier path) {
final var tx = prepareWriteExecution();
}
@Override
- RestconfTransaction prepareWriteExecution() {
- return new NetconfRestconfTransaction(modelContext(), netconfService);
+ RestconfFuture<NormalizedNodePayload> dataGET(final DataPath path, final DataGetParams params) {
+ final var inference = path.inference();
+ final var fields = params.fields();
+ final List<YangInstanceIdentifier> fieldPaths;
+ if (fields != null) {
+ final var tmp = NetconfFieldsTranslator.translate(inference.getEffectiveModelContext(), path.schema(),
+ fields);
+ fieldPaths = tmp == null || tmp.isEmpty() ? null : tmp;
+ } else {
+ fieldPaths = null;
+ }
+
+ final NormalizedNode node;
+ if (fieldPaths != null) {
+ node = readData(params.content(), path.instance(), params.withDefaults(), fieldPaths);
+ } else {
+ node = readData(params.content(), path.instance(), params.withDefaults());
+ }
+ return completeDataGET(inference, QueryParameters.of(params), node);
}
@Override
};
}
- @Override
- ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store,
+ private ListenableFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store,
final YangInstanceIdentifier path, final List<YangInstanceIdentifier> fields) {
return switch (store) {
case CONFIGURATION -> netconfService.getConfig(path, fields);
};
}
+ /**
+ * Read specific type of data from data store via transaction with specified subtrees that should only be read.
+ * Close {@link DOMTransactionChain} inside of object {@link RestconfStrategy} provided as a parameter.
+ *
+ * @param content type of data to read (config, state, all)
+ * @param path the parent path to read
+ * @param withDefa value of with-defaults parameter
+ * @param fields paths to selected subtrees which should be read, relative to to the parent path
+ * @return {@link NormalizedNode}
+ */
+ // FIXME: NETCONF-1155: this method should asynchronous
+ public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
+ final @NonNull YangInstanceIdentifier path, final @Nullable WithDefaultsParam withDefa,
+ final @NonNull List<YangInstanceIdentifier> fields) {
+ return switch (content) {
+ case ALL -> {
+ // PREPARE STATE DATA NODE
+ final var stateDataNode = readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
+ // PREPARE CONFIG DATA NODE
+ final var configDataNode = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
+
+ yield mergeConfigAndSTateDataIfNeeded(stateDataNode, withDefa == null ? configDataNode
+ : prepareDataByParamWithDef(configDataNode, path, withDefa.mode()));
+ }
+ case CONFIG -> {
+ final var read = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
+ yield withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa.mode());
+ }
+ case NONCONFIG -> readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
+ };
+ }
+
+ /**
+ * Read specific type of data {@link LogicalDatastoreType} via transaction in {@link RestconfStrategy} with
+ * specified subtrees that should only be read.
+ *
+ * @param store datastore type
+ * @param path parent path to selected fields
+ * @param closeTransactionChain if it is set to {@code true}, after transaction it will close transactionChain
+ * in {@link RestconfStrategy} if any
+ * @param fields paths to selected subtrees which should be read, relative to to the parent path
+ * @return {@link NormalizedNode}
+ */
+ private @Nullable NormalizedNode readDataViaTransaction(final @NonNull LogicalDatastoreType store,
+ final @NonNull YangInstanceIdentifier path, final @NonNull List<YangInstanceIdentifier> fields) {
+ return TransactionUtil.syncAccess(read(store, path, fields), path).orElse(null);
+ }
+
@Override
ListenableFuture<Boolean> exists(final YangInstanceIdentifier path) {
return Futures.transform(remapException(netconfService.getConfig(path)),
import static java.util.Objects.requireNonNull;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.io.CharSource;
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.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Optional;
+import java.util.concurrent.CancellationException;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionException;
+import org.opendaylight.mdsal.dom.api.DOMActionResult;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcResult;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
+import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
+import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.query.ContentParam;
import org.opendaylight.restconf.api.query.WithDefaultsParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.common.patch.PatchStatusEntity;
import org.opendaylight.restconf.nb.rfc8040.Insert;
+import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
+import org.opendaylight.restconf.nb.rfc8040.databind.ResourceBody;
+import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
+import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.YangInstanceIdentifierSerializer;
+import org.opendaylight.restconf.server.api.DataGetParams;
+import org.opendaylight.restconf.server.api.DataPostPath;
+import org.opendaylight.restconf.server.api.DataPostResult;
import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
+import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
+import org.opendaylight.restconf.server.api.DataPutPath;
import org.opendaylight.restconf.server.api.DataPutResult;
import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.api.OperationsGetResult;
+import org.opendaylight.restconf.server.api.OperationsPostPath;
import org.opendaylight.restconf.server.api.OperationsPostResult;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath.Rpc;
import org.opendaylight.restconf.server.spi.OperationInput;
import org.opendaylight.restconf.server.spi.RpcImplementation;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.with.defaults.rev110601.WithDefaultsMode;
import org.opendaylight.yangtools.yang.common.ErrorType;
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.common.XMLNamespace;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleEffectiveStatement;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
import org.opendaylight.yangtools.yang.model.repo.api.YinTextSchemaSource;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// FIXME: it seems the first three operations deal with lifecycle of a transaction, while others invoke various
// operations. This should be handled through proper allocation indirection.
public abstract class RestconfStrategy {
+ @NonNullByDefault
+ public record StrategyAndPath(RestconfStrategy strategy, DataPath path) {
+ public StrategyAndPath {
+ requireNonNull(strategy);
+ requireNonNull(path);
+ }
+ }
+
+ /**
+ * Result of a partial {@link ApiPath} lookup for the purposes of supporting {@code yang-ext:mount}-delimited mount
+ * points with possible nesting.
+ *
+ * @param strategy the strategy to use
+ * @param tail the {@link ApiPath} tail to use with the strategy
+ */
+ @NonNullByDefault
+ public record StrategyAndTail(RestconfStrategy strategy, ApiPath tail) {
+ public StrategyAndTail {
+ requireNonNull(strategy);
+ requireNonNull(tail);
+ }
+ }
+
private static final Logger LOG = LoggerFactory.getLogger(RestconfStrategy.class);
- private final @NonNull DatabindContext databind;
private final @NonNull ImmutableMap<QName, RpcImplementation> localRpcs;
+ private final @NonNull ApiPathNormalizer pathNormalizer;
+ private final @NonNull DatabindContext databind;
private final DOMYangTextSourceProvider sourceProvider;
+ private final DOMMountPointService mountPointService;
+ private final DOMActionService actionService;
private final DOMRpcService rpcService;
RestconfStrategy(final DatabindContext databind, final ImmutableMap<QName, RpcImplementation> localRpcs,
- final @Nullable DOMRpcService rpcService, final DOMYangTextSourceProvider sourceProvider) {
+ final @Nullable DOMRpcService rpcService, final @Nullable DOMActionService actionService,
+ final DOMYangTextSourceProvider sourceProvider, final @Nullable DOMMountPointService mountPointService) {
this.databind = requireNonNull(databind);
this.localRpcs = requireNonNull(localRpcs);
this.rpcService = rpcService;
+ this.actionService = actionService;
this.sourceProvider = sourceProvider;
+ this.mountPointService = mountPointService;
+ pathNormalizer = new ApiPathNormalizer(databind);
+ }
+
+ public final @NonNull StrategyAndPath resolveStrategyPath(final ApiPath path) {
+ final var andTail = resolveStrategy(path);
+ final var strategy = andTail.strategy();
+ return new StrategyAndPath(strategy, strategy.pathNormalizer.normalizeDataPath(andTail.tail()));
}
/**
- * Look up the appropriate strategy for a particular mount point.
+ * Resolve any and all {@code yang-ext:mount} to the target {@link StrategyAndTail}.
*
- * @param databind {@link DatabindContext} of target mount point
- * @param mountPoint Target mount point
- * @return A strategy, or null if the mount point does not expose a supported interface
- * @throws NullPointerException if any argument is {@code null}
+ * @param path {@link ApiPath} to resolve
+ * @return A strategy and the remaining path
+ * @throws NullPointerException if {@code path} is {@code null}
*/
- public static @Nullable RestconfStrategy forMountPoint(final DatabindContext databind,
- final DOMMountPoint mountPoint) {
+ public final @NonNull StrategyAndTail resolveStrategy(final ApiPath path) {
+ var mount = path.indexOf("yang-ext", "mount");
+ if (mount == -1) {
+ return new StrategyAndTail(this, path);
+ }
+ if (mountPointService == null) {
+ throw new RestconfDocumentedException("Mount point service is not available",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED);
+ }
+ final var mountPath = path.subPath(0, mount);
+ final var dataPath = pathNormalizer.normalizeDataPath(path.subPath(0, mount));
+ final var mountPoint = mountPointService.getMountPoint(dataPath.instance())
+ .orElseThrow(() -> new RestconfDocumentedException("Mount point '" + mountPath + "' does not exist",
+ ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
+
+ return createStrategy(mountPath, mountPoint).resolveStrategy(path.subPath(mount + 1));
+ }
+
+ private static @NonNull RestconfStrategy createStrategy(final ApiPath mountPath, final DOMMountPoint mountPoint) {
+ final var mountSchemaService = mountPoint.getService(DOMSchemaService.class)
+ .orElseThrow(() -> new RestconfDocumentedException(
+ "Mount point '" + mountPath + "' does not expose DOMSchemaService",
+ ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT));
+ final var mountModelContext = mountSchemaService.getGlobalContext();
+ if (mountModelContext == null) {
+ throw new RestconfDocumentedException("Mount point '" + mountPath + "' does not have any models",
+ ErrorType.PROTOCOL, ErrorTags.RESOURCE_DENIED_TRANSPORT);
+ }
+ final var mountDatabind = DatabindContext.ofModel(mountModelContext);
+ final var mountPointService = mountPoint.getService(DOMMountPointService.class).orElse(null);
final var rpcService = mountPoint.getService(DOMRpcService.class).orElse(null);
+ final var actionService = mountPoint.getService(DOMActionService.class).orElse(null);
final var sourceProvider = mountPoint.getService(DOMSchemaService.class)
.flatMap(schema -> Optional.ofNullable(schema.getExtensions().getInstance(DOMYangTextSourceProvider.class)))
.orElse(null);
final var netconfService = mountPoint.getService(NetconfDataTreeService.class);
if (netconfService.isPresent()) {
- return new NetconfRestconfStrategy(databind, netconfService.orElseThrow(), rpcService, sourceProvider);
+ return new NetconfRestconfStrategy(mountDatabind, netconfService.orElseThrow(), rpcService, actionService,
+ sourceProvider, mountPointService);
}
final var dataBroker = mountPoint.getService(DOMDataBroker.class);
if (dataBroker.isPresent()) {
- return new MdsalRestconfStrategy(databind, dataBroker.orElseThrow(), rpcService, sourceProvider);
+ return new MdsalRestconfStrategy(mountDatabind, dataBroker.orElseThrow(), rpcService, actionService,
+ sourceProvider, mountPointService);
}
- return null;
+ LOG.warn("Mount point {} does not expose a suitable access interface", mountPath);
+ throw new RestconfDocumentedException("Could not find a supported access interface in mount point",
+ ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, mountPoint.getIdentifier());
}
public final @NonNull DatabindContext databind() {
*/
abstract ListenableFuture<Optional<NormalizedNode>> read(LogicalDatastoreType store, YangInstanceIdentifier path);
- /**
- * Read data selected using fields from the datastore.
- *
- * @param store the logical data store which should be modified
- * @param path the parent data object path
- * @param fields paths to selected fields relative to parent path
- * @return a ListenableFuture containing the result of the read
- */
- abstract ListenableFuture<Optional<NormalizedNode>> read(LogicalDatastoreType store, YangInstanceIdentifier path,
- List<YangInstanceIdentifier> fields);
-
/**
* Check if data already exists in the configuration datastore.
*
}, MoreExecutors.directExecutor());
}
+ public @NonNull RestconfFuture<DataPutResult> dataPUT(final ApiPath apiPath, final ResourceBody body,
+ final Map<String, String> queryParameters) {
+ final var path = pathNormalizer.normalizeDataPath(apiPath);
+
+ final Insert insert;
+ try {
+ insert = Insert.ofQueryParameters(databind, queryParameters);
+ } catch (IllegalArgumentException e) {
+ return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
+ }
+ final NormalizedNode data;
+ try {
+ data = body.toNormalizedNode(new DataPutPath(databind, path.inference(), path.instance()));
+ } catch (RestconfDocumentedException e) {
+ return RestconfFuture.failed(e);
+ }
+ return putData(path.instance(), data, insert);
+ }
+
/**
* Check mount point and prepare variables for put data to DS.
*
* @param insert {@link Insert}
* @return A {@link DataPutResult}
*/
- public final RestconfFuture<DataPutResult> putData(final YangInstanceIdentifier path,
+ public final @NonNull RestconfFuture<DataPutResult> putData(final YangInstanceIdentifier path,
final NormalizedNode data, final @Nullable Insert insert) {
final var exists = TransactionUtil.syncAccess(exists(path), path);
}
}
+ public final @NonNull RestconfFuture<NormalizedNodePayload> dataGET(final ApiPath apiPath,
+ final DataGetParams params) {
+ return dataGET(pathNormalizer.normalizeDataPath(apiPath), params);
+ }
+
+ abstract @NonNull RestconfFuture<NormalizedNodePayload> dataGET(DataPath path, DataGetParams params);
+
+ static final @NonNull RestconfFuture<NormalizedNodePayload> completeDataGET(final Inference inference,
+ final QueryParameters queryParams, final NormalizedNode node) {
+ if (node == null) {
+ return RestconfFuture.failed(new RestconfDocumentedException(
+ "Request could not be completed because the relevant data model content does not exist",
+ ErrorType.PROTOCOL, ErrorTag.DATA_MISSING));
+ }
+
+ return RestconfFuture.of(new NormalizedNodePayload(inference, node, queryParams));
+ }
+
/**
* Read specific type of data from data store via transaction. Close {@link DOMTransactionChain} if any
* inside of object {@link RestconfStrategy} provided as a parameter.
* @return {@link NormalizedNode}
*/
// FIXME: NETCONF-1155: this method should asynchronous
- public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
+ public final @Nullable NormalizedNode readData(final @NonNull ContentParam content,
final @NonNull YangInstanceIdentifier path, final WithDefaultsParam defaultsMode) {
return switch (content) {
case ALL -> {
};
}
- /**
- * Read specific type of data from data store via transaction with specified subtrees that should only be read.
- * Close {@link DOMTransactionChain} inside of object {@link RestconfStrategy} provided as a parameter.
- *
- * @param content type of data to read (config, state, all)
- * @param path the parent path to read
- * @param withDefa value of with-defaults parameter
- * @param fields paths to selected subtrees which should be read, relative to to the parent path
- * @return {@link NormalizedNode}
- */
- // FIXME: NETCONF-1155: this method should asynchronous
- public @Nullable NormalizedNode readData(final @NonNull ContentParam content,
- final @NonNull YangInstanceIdentifier path, final @Nullable WithDefaultsParam withDefa,
- final @NonNull List<YangInstanceIdentifier> fields) {
- return switch (content) {
- case ALL -> {
- // PREPARE STATE DATA NODE
- final var stateDataNode = readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
- // PREPARE CONFIG DATA NODE
- final var configDataNode = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
-
- yield mergeConfigAndSTateDataIfNeeded(stateDataNode, withDefa == null ? configDataNode
- : prepareDataByParamWithDef(configDataNode, path, withDefa.mode()));
- }
- case CONFIG -> {
- final var read = readDataViaTransaction(LogicalDatastoreType.CONFIGURATION, path, fields);
- yield withDefa == null ? read : prepareDataByParamWithDef(read, path, withDefa.mode());
- }
- case NONCONFIG -> readDataViaTransaction(LogicalDatastoreType.OPERATIONAL, path, fields);
- };
- }
-
private @Nullable NormalizedNode readDataViaTransaction(final LogicalDatastoreType store,
final YangInstanceIdentifier path) {
return TransactionUtil.syncAccess(read(store, path), path).orElse(null);
}
- /**
- * Read specific type of data {@link LogicalDatastoreType} via transaction in {@link RestconfStrategy} with
- * specified subtrees that should only be read.
- *
- * @param store datastore type
- * @param path parent path to selected fields
- * @param closeTransactionChain if it is set to {@code true}, after transaction it will close transactionChain
- * in {@link RestconfStrategy} if any
- * @param fields paths to selected subtrees which should be read, relative to to the parent path
- * @return {@link NormalizedNode}
- */
- private @Nullable NormalizedNode readDataViaTransaction(final @NonNull LogicalDatastoreType store,
- final @NonNull YangInstanceIdentifier path, final @NonNull List<YangInstanceIdentifier> fields) {
- return TransactionUtil.syncAccess(read(store, path, fields), path).orElse(null);
- }
-
- private NormalizedNode prepareDataByParamWithDef(final NormalizedNode readData, final YangInstanceIdentifier path,
+ final NormalizedNode prepareDataByParamWithDef(final NormalizedNode readData, final YangInstanceIdentifier path,
final WithDefaultsMode defaultsMode) {
final boolean trim = switch (defaultsMode) {
case Trim -> true;
return childCtx;
}
- private static NormalizedNode mergeConfigAndSTateDataIfNeeded(final NormalizedNode stateDataNode,
- final NormalizedNode configDataNode) {
+ static final NormalizedNode mergeConfigAndSTateDataIfNeeded(final NormalizedNode stateDataNode,
+ final NormalizedNode configDataNode) {
// if no data exists
if (stateDataNode == null && configDataNode == null) {
return null;
y -> builder.addChild((T) prepareData(y.getValue(), stateMap.get(y.getKey()))));
}
- public @NonNull RestconfFuture<OperationsPostResult> invokeRpc(final URI restconfURI, final QName type,
- final OperationInput input) {
+ public @NonNull RestconfFuture<OperationsGetResult> operationsGET() {
+ final var modelContext = modelContext();
+ final var modules = modelContext.getModuleStatements();
+ if (modules.isEmpty()) {
+ // No modules, or defensive return empty content
+ return RestconfFuture.of(new OperationsGetResult.Container(modelContext, ImmutableSetMultimap.of()));
+ }
+
+ // RPC QNames by their XMLNamespace/Revision. This should be a Table, but Revision can be null, which wrecks us.
+ final var table = new HashMap<XMLNamespace, Map<Revision, ImmutableSet<QName>>>();
+ for (var entry : modules.entrySet()) {
+ final var module = entry.getValue();
+ final var rpcNames = module.streamEffectiveSubstatements(RpcEffectiveStatement.class)
+ .map(RpcEffectiveStatement::argument)
+ .collect(ImmutableSet.toImmutableSet());
+ if (!rpcNames.isEmpty()) {
+ final var namespace = entry.getKey();
+ table.computeIfAbsent(namespace.getNamespace(), ignored -> new HashMap<>())
+ .put(namespace.getRevision().orElse(null), rpcNames);
+ }
+ }
+
+ // Now pick the latest revision for each namespace
+ final var rpcs = ImmutableSetMultimap.<QNameModule, QName>builder();
+ for (var entry : table.entrySet()) {
+ entry.getValue().entrySet().stream()
+ .sorted(Comparator.comparing(Entry::getKey, (first, second) -> Revision.compare(second, first)))
+ .findFirst()
+ .ifPresent(row -> rpcs.putAll(QNameModule.create(entry.getKey(), row.getKey()), row.getValue()));
+ }
+ return RestconfFuture.of(new OperationsGetResult.Container(modelContext, rpcs.build()));
+ }
+
+ public @NonNull RestconfFuture<OperationsGetResult> operationsGET(final ApiPath apiPath) {
+ if (apiPath.steps().isEmpty()) {
+ return operationsGET();
+ }
+
+ final Rpc rpc;
+ try {
+ rpc = pathNormalizer.normalizeRpcPath(apiPath);
+ } catch (RestconfDocumentedException e) {
+ return RestconfFuture.failed(e);
+ }
+
+ return RestconfFuture.of(
+ new OperationsGetResult.Leaf(rpc.inference().getEffectiveModelContext(), rpc.rpc().argument()));
+ }
+
+ public @NonNull RestconfFuture<OperationsPostResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
+ final OperationInputBody body) {
+ final OperationPath.Rpc path;
+ try {
+ path = pathNormalizer.normalizeRpcPath(apiPath);
+ } catch (RestconfDocumentedException e) {
+ return RestconfFuture.failed(e);
+ }
+
+ final var postPath = new OperationsPostPath(databind, path.inference());
+ final ContainerNode data;
+ try {
+ data = body.toContainerNode(postPath);
+ } catch (IOException e) {
+ LOG.debug("Error reading input", e);
+ return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
+ }
+
+ final var type = path.rpc().argument();
final var local = localRpcs.get(type);
if (local != null) {
- return local.invoke(restconfURI, input);
+ return local.invoke(restconfURI, new OperationInput(databind, postPath.operation(), data));
}
if (rpcService == null) {
LOG.debug("RPC invocation is not available");
}
final var ret = new SettableRestconfFuture<OperationsPostResult>();
- Futures.addCallback(rpcService.invokeRpc(requireNonNull(type), input.input()),
- new FutureCallback<DOMRpcResult>() {
- @Override
- public void onSuccess(final DOMRpcResult response) {
- final var errors = response.errors();
- if (errors.isEmpty()) {
- ret.set(input.newOperationOutput(response.value()));
- } else {
- LOG.debug("RPC invocation reported {}", response.errors());
- ret.setFailure(new RestconfDocumentedException("RPC implementation reported errors", null,
- response.errors()));
- }
+ Futures.addCallback(rpcService.invokeRpc(type, data), new FutureCallback<DOMRpcResult>() {
+ @Override
+ public void onSuccess(final DOMRpcResult response) {
+ final var errors = response.errors();
+ if (errors.isEmpty()) {
+ ret.set(new OperationsPostResult(databind, postPath.operation(), response.value()));
+ } else {
+ LOG.debug("RPC invocation reported {}", response.errors());
+ ret.setFailure(new RestconfDocumentedException("RPC implementation reported errors", null,
+ response.errors()));
}
+ }
- @Override
- public void onFailure(final Throwable cause) {
- LOG.debug("RPC invocation failed, cause");
- if (cause instanceof RestconfDocumentedException ex) {
- ret.setFailure(ex);
- } else {
- // TODO: YangNetconfErrorAware if we ever get into a broader invocation scope
- ret.setFailure(new RestconfDocumentedException(cause,
- new RestconfError(ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage())));
- }
+ @Override
+ public void onFailure(final Throwable cause) {
+ LOG.debug("RPC invocation failed, cause");
+ if (cause instanceof RestconfDocumentedException ex) {
+ ret.setFailure(ex);
+ } else {
+ // TODO: YangNetconfErrorAware if we ever get into a broader invocation scope
+ ret.setFailure(new RestconfDocumentedException(cause,
+ new RestconfError(ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage())));
}
- }, MoreExecutors.directExecutor());
+ }
+ }, MoreExecutors.directExecutor());
return ret;
}
ErrorType.APPLICATION, ErrorTag.DATA_MISSING));
}
+ public final @NonNull RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath apiPath,
+ final DataPostBody body, final Map<String, String> queryParameters) {
+ if (apiPath.steps().isEmpty()) {
+ return dataCreatePOST(body.toResource(), queryParameters);
+ }
+ final var path = pathNormalizer.normalizeDataOrActionPath(apiPath);
+ if (path instanceof DataPath dataPath) {
+ try (var resourceBody = body.toResource()) {
+ return dataCreatePOST(new DataPostPath(databind, dataPath.inference(), dataPath.instance()),
+ resourceBody, queryParameters);
+ }
+ }
+ if (path instanceof OperationPath.Action actionPath) {
+ try (var inputBody = body.toOperationInput()) {
+ return dataInvokePOST(actionPath, inputBody);
+ }
+ }
+ // Note: this should never happen
+ // FIXME: we should be able to eliminate this path with Java 21+ pattern matching
+ return RestconfFuture.failed(new RestconfDocumentedException("Unhandled path " + path));
+ }
+
+ public @NonNull RestconfFuture<CreateResource> dataCreatePOST(final ChildBody body,
+ final Map<String, String> queryParameters) {
+ return dataCreatePOST(new DataPostPath(databind,
+ SchemaInferenceStack.of(databind.modelContext()).toInference(), YangInstanceIdentifier.of()), body,
+ queryParameters);
+ }
+
+ private @NonNull RestconfFuture<CreateResource> dataCreatePOST(final DataPostPath path, final ChildBody body,
+ final Map<String, String> queryParameters) {
+ final Insert insert;
+ try {
+ insert = Insert.ofQueryParameters(path.databind(), queryParameters);
+ } catch (IllegalArgumentException e) {
+ return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
+ }
+
+ final var payload = body.toPayload(path);
+ return postData(concat(path.instance(), payload.prefix()), payload.body(), insert);
+ }
+
+ private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
+ var ret = parent;
+ for (var arg : args) {
+ ret = ret.node(arg);
+ }
+ return ret;
+ }
+
+ private @NonNull RestconfFuture<InvokeOperation> dataInvokePOST(final OperationPath.Action path,
+ final OperationInputBody body) {
+ final var inference = path.inference();
+ final ContainerNode input;
+ try {
+ input = body.toContainerNode(new OperationsPostPath(databind, inference));
+ } catch (IOException e) {
+ LOG.debug("Error reading input", e);
+ return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
+ }
+
+ if (actionService == null) {
+ return RestconfFuture.failed(new RestconfDocumentedException("DOMActionService is missing."));
+ }
+
+ final var future = dataInvokePOST(actionService, path, input);
+ return future.transform(result -> result.getOutput()
+ .flatMap(output -> output.isEmpty() ? Optional.empty()
+ : Optional.of(new InvokeOperation(new NormalizedNodePayload(inference, output))))
+ .orElse(InvokeOperation.EMPTY));
+ }
+
+ /**
+ * Invoke Action via ActionServiceHandler.
+ *
+ * @param input input data
+ * @param yangIId invocation context
+ * @param schemaPath schema path of data
+ * @param actionService action service to invoke action
+ * @return {@link DOMActionResult}
+ */
+ private static RestconfFuture<DOMActionResult> dataInvokePOST(final DOMActionService actionService,
+ final OperationPath.Action path, final @NonNull ContainerNode input) {
+ final var ret = new SettableRestconfFuture<DOMActionResult>();
+
+ Futures.addCallback(actionService.invokeAction(
+ path.inference().toSchemaInferenceStack().toSchemaNodeIdentifier(),
+ new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, path.instance()), input),
+ new FutureCallback<DOMActionResult>() {
+ @Override
+ public void onSuccess(final DOMActionResult result) {
+ final var errors = result.getErrors();
+ LOG.debug("InvokeAction Error Message {}", errors);
+ if (errors.isEmpty()) {
+ ret.set(result);
+ } else {
+ ret.setFailure(new RestconfDocumentedException("InvokeAction Error Message ", null, errors));
+ }
+ }
+
+ @Override
+ public void onFailure(final Throwable cause) {
+ if (cause instanceof DOMActionException) {
+ ret.set(new SimpleDOMActionResult(List.of(RpcResultBuilder.newError(
+ ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage()))));
+ } else if (cause instanceof RestconfDocumentedException e) {
+ ret.setFailure(e);
+ } else if (cause instanceof CancellationException) {
+ ret.setFailure(new RestconfDocumentedException("Action cancelled while executing",
+ ErrorType.RPC, ErrorTag.PARTIAL_OPERATION, cause));
+ } else {
+ ret.setFailure(new RestconfDocumentedException("Invocation failed", cause));
+ }
+ }
+ }, MoreExecutors.directExecutor());
+
+ return ret;
+ }
}
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
* {@link NetconfDataTreeService}.
*
* @param modelContext EffectiveModelContext
- * @param schemaNode Root DataSchemaNode
+ * @param startNode Root DataSchemaNode
* @param input input value of fields parameter
* @return {@link List} of {@link YangInstanceIdentifier} that are relative to the last {@link PathArgument}
* of provided {@code identifier}
*/
public static @NonNull List<YangInstanceIdentifier> translate(
- final @NonNull EffectiveModelContext modelContext, final @NonNull DataSchemaNode schemaNode,
+ final @NonNull EffectiveModelContext modelContext, final @NonNull DataSchemaContext startNode,
final @NonNull FieldsParam input) {
- return parseFields(modelContext, schemaNode, input).stream()
- .map(NetconfFieldsTranslator::buildPath)
- .toList();
- }
-
- private static @NonNull Set<LinkedPathElement> parseFields(final @NonNull EffectiveModelContext modelContext,
- final @NonNull DataSchemaNode schemaNode, final @NonNull FieldsParam input) {
- final DataSchemaContext startNode;
- try {
- startNode = DataSchemaContext.of(schemaNode);
- } catch (IllegalStateException e) {
- throw new RestconfDocumentedException(
- "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e);
- }
-
final var parsed = new HashSet<LinkedPathElement>();
- processSelectors(parsed, modelContext, schemaNode.getQName().getModule(),
+ processSelectors(parsed, modelContext, startNode.dataSchemaNode().getQName().getModule(),
new LinkedPathElement(null, List.of(), startNode), input.nodeSelectors());
-
- return parsed;
+ return parsed.stream().map(NetconfFieldsTranslator::buildPath).toList();
}
private static void processSelectors(final Set<LinkedPathElement> parsed, final EffectiveModelContext context,
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
/**
* {@link ParameterAwareNormalizedNodeWriter}.
*
* @param modelContext EffectiveModelContext
- * @param schemaNode Root DataSchemaNode
+ * @param startNode {@link DataSchemaContext} of the API request path
* @param input input value of fields parameter
* @return {@link List} of levels; each level contains set of {@link QName}
*/
public static @NonNull List<Set<QName>> translate(final @NonNull EffectiveModelContext modelContext,
- final DataSchemaNode schemaNode, final @NonNull FieldsParam input) {
- if (schemaNode == null) {
- throw new RestconfDocumentedException(
- "Start node missing in " + input, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE);
- }
+ final DataSchemaContext startNode, final @NonNull FieldsParam input) {
final var parsed = new ArrayList<Set<QName>>();
- processSelectors(parsed, modelContext, schemaNode.getQName().getModule(), DataSchemaContext.of(schemaNode),
+ processSelectors(parsed, modelContext, startNode.dataSchemaNode().getQName().getModule(), startNode,
input.nodeSelectors(), 0);
return parsed;
}
requireNonNull(inference);
requireNonNull(instance);
}
+
+ public DataPostPath(final DatabindContext databind) {
+ this(databind, Inference.ofDataTreePath(databind.modelContext()), YangInstanceIdentifier.of());
+ }
}
requireNonNull(inference);
requireNonNull(instance);
}
+
+ public DataPutPath(final DatabindContext databind) {
+ this(databind, Inference.ofDataTreePath(databind.modelContext()), YangInstanceIdentifier.of());
+ }
}
import static com.google.common.base.Verify.verifyNotNull;
import static java.util.Objects.requireNonNull;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Maps;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.net.URI;
import java.time.format.DateTimeParseException;
-import java.util.Comparator;
-import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.concurrent.CancellationException;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.dom.api.DOMActionException;
-import org.opendaylight.mdsal.dom.api.DOMActionResult;
import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
-import org.opendaylight.mdsal.dom.api.DOMMountPoint;
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
-import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfFuture;
-import org.opendaylight.restconf.common.errors.SettableRestconfFuture;
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.common.patch.PatchStatusContext;
-import org.opendaylight.restconf.nb.rfc8040.Insert;
import org.opendaylight.restconf.nb.rfc8040.databind.ChildBody;
import org.opendaylight.restconf.nb.rfc8040.databind.DataPostBody;
import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
import org.opendaylight.restconf.nb.rfc8040.databind.PatchBody;
import org.opendaylight.restconf.nb.rfc8040.databind.ResourceBody;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
-import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.NetconfFieldsTranslator;
-import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.StrategyAndTail;
import org.opendaylight.restconf.server.api.DataGetParams;
import org.opendaylight.restconf.server.api.DataPatchPath;
-import org.opendaylight.restconf.server.api.DataPostPath;
import org.opendaylight.restconf.server.api.DataPostResult;
import org.opendaylight.restconf.server.api.DataPostResult.CreateResource;
-import org.opendaylight.restconf.server.api.DataPostResult.InvokeOperation;
import org.opendaylight.restconf.server.api.DataPutPath;
import org.opendaylight.restconf.server.api.DataPutResult;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.api.ModulesGetResult;
import org.opendaylight.restconf.server.api.OperationsGetResult;
-import org.opendaylight.restconf.server.api.OperationsPostPath;
import org.opendaylight.restconf.server.api.OperationsPostResult;
import org.opendaylight.restconf.server.api.RestconfServer;
import org.opendaylight.restconf.server.spi.DatabindProvider;
-import org.opendaylight.restconf.server.spi.OperationInput;
import org.opendaylight.restconf.server.spi.RpcImplementation;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.YangApi;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.restconf.Restconf;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
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.common.XMLNamespace;
import org.opendaylight.yangtools.yang.common.YangNames;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
-import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextListener;
-import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
}
}
- // FIXME: Remove this constant. All logic relying on this constant should instead rely on YangInstanceIdentifier
- // equivalent coming out of argument parsing. This may require keeping List<YangInstanceIdentifier> as the
- // nested path split on yang-ext:mount. This splitting needs to be based on consulting the
- // EffectiveModelContext and allowing it only where yang-ext:mount is actually used in models.
- @Deprecated(forRemoval = true)
- private static final String MOUNT = "yang-ext:mount";
- @Deprecated(forRemoval = true)
- private static final Splitter SLASH_SPLITTER = Splitter.on('/');
-
private final @NonNull ImmutableMap<QName, RpcImplementation> localRpcs;
private final @NonNull DOMMountPointService mountPointService;
private final @NonNull DOMDataBroker dataBroker;
}
private @NonNull MdsalRestconfStrategy createLocalStrategy(final EffectiveModelContext modelContext) {
- return new MdsalRestconfStrategy(DatabindContext.ofModel(modelContext), dataBroker, rpcService, sourceProvider,
- localRpcs);
+ return new MdsalRestconfStrategy(DatabindContext.ofModel(modelContext), dataBroker, rpcService, actionService,
+ sourceProvider, mountPointService, localRpcs);
}
private @NonNull MdsalRestconfStrategy localStrategy() {
return verifyNotNull((MdsalRestconfStrategy) LOCAL_STRATEGY.getAcquire(this));
}
- @Deprecated(forRemoval = true)
- private @NonNull MdsalRestconfStrategy localStrategy(final DatabindContext databind) {
- final var local = localStrategy();
- return local.databind().equals(databind) ? local
- : new MdsalRestconfStrategy(databind, dataBroker, rpcService, sourceProvider, localRpcs);
- }
-
@PreDestroy
@Deactivate
@Override
@Override
public RestconfFuture<Empty> dataDELETE(final ApiPath identifier) {
- final var reqPath = bindRequestPath(identifier);
- final var strategy = getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint());
- return strategy.delete(reqPath.getInstanceIdentifier());
+ final var stratAndPath = localStrategy().resolveStrategyPath(identifier);
+ return stratAndPath.strategy().delete(stratAndPath.path().instance());
}
@Override
public RestconfFuture<NormalizedNodePayload> dataGET(final DataGetParams params) {
- return readData(bindRequestRoot(), params);
+ return localStrategy().dataGET(ApiPath.empty(), params);
}
@Override
public RestconfFuture<NormalizedNodePayload> dataGET(final ApiPath identifier, final DataGetParams params) {
- return readData(bindRequestPath(identifier), params);
- }
-
- private @NonNull RestconfFuture<NormalizedNodePayload> readData(final InstanceIdentifierContext reqPath,
- final DataGetParams params) {
- final var fields = params.fields();
- final QueryParameters queryParams;
- if (fields != null) {
- final var modelContext = reqPath.databind().modelContext();
- final var schemaNode = (DataSchemaNode) reqPath.getSchemaNode();
- if (reqPath.getMountPoint() != null) {
- queryParams = QueryParameters.ofFieldPaths(params, NetconfFieldsTranslator.translate(modelContext,
- schemaNode, fields));
- } else {
- queryParams = QueryParameters.ofFields(params, WriterFieldsTranslator.translate(modelContext,
- schemaNode, fields));
- }
- } else {
- queryParams = QueryParameters.of(params);
- }
-
- final var fieldPaths = queryParams.fieldPaths();
- final var strategy = getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint());
- final NormalizedNode node;
- if (fieldPaths != null && !fieldPaths.isEmpty()) {
- node = strategy.readData(params.content(), reqPath.getInstanceIdentifier(), params.withDefaults(),
- fieldPaths);
- } else {
- node = strategy.readData(params.content(), reqPath.getInstanceIdentifier(), params.withDefaults());
- }
- if (node == null) {
- return RestconfFuture.failed(new RestconfDocumentedException(
- "Request could not be completed because the relevant data model content does not exist",
- ErrorType.PROTOCOL, ErrorTag.DATA_MISSING));
- }
-
- return RestconfFuture.of(new NormalizedNodePayload(reqPath.inference(), node, queryParams));
+ final var stratAndTail = localStrategy().resolveStrategy(identifier);
+ return stratAndTail.strategy().dataGET(stratAndTail.tail(), params);
}
@Override
public RestconfFuture<Empty> dataPATCH(final ResourceBody body) {
- return dataPATCH(bindRequestRoot(), body);
+ final var strategy = localStrategy();
+ return dataPATCH(strategy, new DataPutPath(strategy.databind()), body);
}
@Override
public RestconfFuture<Empty> dataPATCH(final ApiPath identifier, final ResourceBody body) {
- return dataPATCH(bindRequestPath(identifier), body);
+ final var strategyAndPath = localStrategy().resolveStrategyPath(identifier);
+ final var strategy = strategyAndPath.strategy();
+ final var path = strategyAndPath.path();
+ return dataPATCH(strategy, new DataPutPath(strategy.databind(), path.inference(), path.instance()), body);
}
- private @NonNull RestconfFuture<Empty> dataPATCH(final InstanceIdentifierContext reqPath, final ResourceBody body) {
- final var req = bindResourceRequest(reqPath, body);
- return req.strategy().merge(req.path(), req.data());
+ private static @NonNull RestconfFuture<Empty> dataPATCH(final RestconfStrategy strategy, final DataPutPath path,
+ final ResourceBody body) {
+ final NormalizedNode data;
+ try {
+ data = body.toNormalizedNode(path);
+ } catch (RestconfDocumentedException e) {
+ return RestconfFuture.failed(e);
+ }
+ return strategy.merge(path.instance(), data);
}
@Override
public RestconfFuture<PatchStatusContext> dataPATCH(final PatchBody body) {
- return dataPATCH(bindRequestRoot(), body);
+ final var strategy = localStrategy();
+ return dataPATCH(strategy, new DataPatchPath(strategy.databind(), YangInstanceIdentifier.of()), body);
}
@Override
public RestconfFuture<PatchStatusContext> dataPATCH(final ApiPath identifier, final PatchBody body) {
- return dataPATCH(bindRequestPath(identifier), body);
+ final var stratAndPath = localStrategy().resolveStrategyPath(identifier);
+ final var strategy = stratAndPath.strategy();
+ return dataPATCH(strategy, new DataPatchPath(strategy.databind(), stratAndPath.path().instance()), body);
}
- private @NonNull RestconfFuture<PatchStatusContext> dataPATCH(final InstanceIdentifierContext reqPath,
- final PatchBody body) {
- final var patchPath = new DataPatchPath(reqPath.databind(), reqPath.getInstanceIdentifier());
+ private static @NonNull RestconfFuture<PatchStatusContext> dataPATCH(final RestconfStrategy strategy,
+ final DataPatchPath path, final PatchBody body) {
final PatchContext patch;
try {
- patch = body.toPatchContext(patchPath);
+ patch = body.toPatchContext(path);
} catch (IOException e) {
LOG.debug("Error parsing YANG Patch input", e);
return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
}
- return getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint()).patchData(patch);
+ return strategy.patchData(patch);
}
@Override
public RestconfFuture<CreateResource> dataPOST(final ChildBody body, final Map<String, String> queryParameters) {
- return dataCreatePOST(bindRequestRoot(), body, queryParameters);
+ return localStrategy().dataCreatePOST(body, queryParameters);
}
@Override
public RestconfFuture<? extends DataPostResult> dataPOST(final ApiPath identifier, final DataPostBody body,
final Map<String, String> queryParameters) {
- final var reqPath = bindRequestPath(identifier);
- if (reqPath.getSchemaNode() instanceof ActionDefinition) {
- try (var inputBody = body.toOperationInput()) {
- return dataInvokePOST(reqPath, inputBody);
- }
- }
-
- try (var childBody = body.toResource()) {
- return dataCreatePOST(reqPath, childBody, queryParameters);
- }
- }
-
- private @NonNull RestconfFuture<CreateResource> dataCreatePOST(final InstanceIdentifierContext reqPath,
- final ChildBody body, final Map<String, String> queryParameters) {
- final var postPath = new DataPostPath(reqPath.databind(), reqPath.inference(), reqPath.getInstanceIdentifier());
-
- final Insert insert;
- try {
- insert = Insert.ofQueryParameters(postPath.databind(), queryParameters);
- } catch (IllegalArgumentException e) {
- return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
- ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
- }
-
- final var payload = body.toPayload(postPath);
- return getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint())
- .postData(concat(postPath.instance(), payload.prefix()), payload.body(), insert);
- }
-
- private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
- var ret = parent;
- for (var arg : args) {
- ret = ret.node(arg);
- }
- return ret;
- }
-
- private RestconfFuture<InvokeOperation> dataInvokePOST(final InstanceIdentifierContext reqPath,
- final OperationInputBody body) {
- final var postPath = new OperationsPostPath(reqPath.databind(), reqPath.inference());
- final var yangIIdContext = reqPath.getInstanceIdentifier();
- final ContainerNode input;
- try {
- input = body.toContainerNode(postPath);
- } catch (IOException e) {
- LOG.debug("Error reading input", e);
- return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
- ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
- }
-
- final var mountPoint = reqPath.getMountPoint();
- final var schemaPath = postPath.operation().toSchemaInferenceStack().toSchemaNodeIdentifier();
- final var future = mountPoint != null ? dataInvokePOST(input, schemaPath, yangIIdContext, mountPoint)
- : dataInvokePOST(input, schemaPath, yangIIdContext, actionService);
-
- return future.transform(result -> result.getOutput()
- .flatMap(output -> output.isEmpty() ? Optional.empty()
- : Optional.of(new InvokeOperation(new NormalizedNodePayload(reqPath.inference(), output))))
- .orElse(InvokeOperation.EMPTY));
- }
-
- /**
- * Invoke Action via ActionServiceHandler.
- *
- * @param data input data
- * @param yangIId invocation context
- * @param schemaPath schema path of data
- * @param actionService action service to invoke action
- * @return {@link DOMActionResult}
- */
- private static RestconfFuture<DOMActionResult> dataInvokePOST(final ContainerNode data, final Absolute schemaPath,
- final YangInstanceIdentifier yangIId, final DOMActionService actionService) {
- final var ret = new SettableRestconfFuture<DOMActionResult>();
-
- Futures.addCallback(actionService.invokeAction(schemaPath,
- new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, yangIId.getParent()), data),
- new FutureCallback<DOMActionResult>() {
- @Override
- public void onSuccess(final DOMActionResult result) {
- final var errors = result.getErrors();
- LOG.debug("InvokeAction Error Message {}", errors);
- if (errors.isEmpty()) {
- ret.set(result);
- } else {
- ret.setFailure(new RestconfDocumentedException("InvokeAction Error Message ", null, errors));
- }
- }
-
- @Override
- public void onFailure(final Throwable cause) {
- if (cause instanceof DOMActionException) {
- ret.set(new SimpleDOMActionResult(List.of(RpcResultBuilder.newError(
- ErrorType.RPC, ErrorTag.OPERATION_FAILED, cause.getMessage()))));
- } else if (cause instanceof RestconfDocumentedException e) {
- ret.setFailure(e);
- } else if (cause instanceof CancellationException) {
- ret.setFailure(new RestconfDocumentedException("Action cancelled while executing",
- ErrorType.RPC, ErrorTag.PARTIAL_OPERATION, cause));
- } else {
- ret.setFailure(new RestconfDocumentedException("Invocation failed", cause));
- }
- }
- }, MoreExecutors.directExecutor());
-
- return ret;
- }
-
- /**
- * Invoking Action via mount point.
- *
- * @param mountPoint mount point
- * @param data input data
- * @param schemaPath schema path of data
- * @return {@link DOMActionResult}
- */
- private static RestconfFuture<DOMActionResult> dataInvokePOST(final ContainerNode data, final Absolute schemaPath,
- final YangInstanceIdentifier yangIId, final DOMMountPoint mountPoint) {
- final var actionService = mountPoint.getService(DOMActionService.class);
- return actionService.isPresent() ? dataInvokePOST(data, schemaPath, yangIId, actionService.orElseThrow())
- : RestconfFuture.failed(new RestconfDocumentedException("DOMActionService is missing."));
+ final var strategyAndTail = localStrategy().resolveStrategy(identifier);
+ return strategyAndTail.strategy().dataPOST(strategyAndTail.tail(), body, queryParameters);
}
@Override
public RestconfFuture<DataPutResult> dataPUT(final ResourceBody body, final Map<String, String> query) {
- return dataPUT(bindRequestRoot(), body, query);
+ return localStrategy().dataPUT(ApiPath.empty(), body, query);
}
@Override
public RestconfFuture<DataPutResult> dataPUT(final ApiPath identifier, final ResourceBody body,
final Map<String, String> queryParameters) {
- return dataPUT(bindRequestPath(identifier), body, queryParameters);
- }
-
- private @NonNull RestconfFuture<DataPutResult> dataPUT(final InstanceIdentifierContext reqPath,
- final ResourceBody body, final Map<String, String> queryParameters) {
- final Insert insert;
- try {
- insert = Insert.ofQueryParameters(reqPath.databind(), queryParameters);
- } catch (IllegalArgumentException e) {
- return RestconfFuture.failed(new RestconfDocumentedException(e.getMessage(),
- ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE, e));
- }
- final var req = bindResourceRequest(reqPath, body);
- return req.strategy().putData(req.path(), req.data(), insert);
+ final var strategyAndTail = localStrategy().resolveStrategy(identifier);
+ return strategyAndTail.strategy().dataPUT(strategyAndTail.tail(), body, queryParameters);
}
@Override
return RestconfFuture.failed(new RestconfDocumentedException("Mount path has to end with yang-ext:mount"));
}
- final InstanceIdentifierContext point;
- try {
- point = InstanceIdentifierContext.ofApiPath(mountPath, localStrategy().databind(), mountPointService);
- } catch (RestconfDocumentedException e) {
- return RestconfFuture.failed(e);
- }
-
- final RestconfStrategy strategy;
+ final StrategyAndTail stratAndTail;
try {
- strategy = forMountPoint(point.databind(), point.getMountPoint());
+ stratAndTail = localStrategy().resolveStrategy(mountPath);
} catch (RestconfDocumentedException e) {
return RestconfFuture.failed(e);
}
- return modulesGET(strategy, fileName, revision, representation);
+ // FIXME: require remnant to be empty
+ return modulesGET(stratAndTail.strategy(), fileName, revision, representation);
}
private static @NonNull RestconfFuture<ModulesGetResult> modulesGET(final RestconfStrategy strategy,
@Override
public RestconfFuture<OperationsGetResult> operationsGET() {
- return operationsGET(localStrategy().modelContext());
+ return localStrategy().operationsGET();
}
@Override
public RestconfFuture<OperationsGetResult> operationsGET(final ApiPath operation) {
- // get current module RPCs/actions by RPC/action name
- final var inference = bindRequestPath(operation).inference();
- if (inference.isEmpty()) {
- return operationsGET(inference.getEffectiveModelContext());
- }
-
- final var stmt = inference.toSchemaInferenceStack().currentStatement();
- if (stmt instanceof RpcEffectiveStatement rpc) {
- return RestconfFuture.of(
- new OperationsGetResult.Leaf(inference.getEffectiveModelContext(), rpc.argument()));
- }
- return RestconfFuture.failed(new RestconfDocumentedException("RPC not found",
- ErrorType.PROTOCOL, ErrorTag.DATA_MISSING));
- }
-
- private static @NonNull RestconfFuture<OperationsGetResult> operationsGET(
- final EffectiveModelContext modelContext) {
- final var modules = modelContext.getModuleStatements();
- if (modules.isEmpty()) {
- // No modules, or defensive return empty content
- return RestconfFuture.of(new OperationsGetResult.Container(modelContext, ImmutableSetMultimap.of()));
- }
-
- // RPC QNames by their XMLNamespace/Revision. This should be a Table, but Revision can be null, which wrecks us.
- final var table = new HashMap<XMLNamespace, Map<Revision, ImmutableSet<QName>>>();
- for (var entry : modules.entrySet()) {
- final var module = entry.getValue();
- final var rpcNames = module.streamEffectiveSubstatements(RpcEffectiveStatement.class)
- .map(RpcEffectiveStatement::argument)
- .collect(ImmutableSet.toImmutableSet());
- if (!rpcNames.isEmpty()) {
- final var namespace = entry.getKey();
- table.computeIfAbsent(namespace.getNamespace(), ignored -> new HashMap<>())
- .put(namespace.getRevision().orElse(null), rpcNames);
- }
- }
-
- // Now pick the latest revision for each namespace
- final var rpcs = ImmutableSetMultimap.<QNameModule, QName>builder();
- for (var entry : table.entrySet()) {
- entry.getValue().entrySet().stream()
- .sorted(Comparator.comparing(Entry::getKey, (first, second) -> Revision.compare(second, first)))
- .findFirst()
- .ifPresent(row -> rpcs.putAll(QNameModule.create(entry.getKey(), row.getKey()), row.getValue()));
- }
- return RestconfFuture.of(new OperationsGetResult.Container(modelContext, rpcs.build()));
+ final var strategyAndTail = localStrategy().resolveStrategy(operation);
+ return strategyAndTail.strategy().operationsGET(strategyAndTail.tail());
}
@Override
public RestconfFuture<OperationsPostResult> operationsPOST(final URI restconfURI, final ApiPath apiPath,
final OperationInputBody body) {
- final var reqPath = bindRequestPath(localStrategy(), apiPath);
- final var postPath = new OperationsPostPath(reqPath.databind(), reqPath.inference());
-
- final ContainerNode input;
- try {
- input = body.toContainerNode(postPath);
- } catch (IOException e) {
- LOG.debug("Error reading input", e);
- return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
- ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
- }
-
- final var strategy = getRestconfStrategy(reqPath.databind(), reqPath.getMountPoint());
- return strategy.invokeRpc(restconfURI, reqPath.getSchemaNode().getQName(),
- new OperationInput(strategy.databind(), postPath.operation(), input));
+ final var strategyAndTail = localStrategy().resolveStrategy(apiPath);
+ final var strategy = strategyAndTail.strategy();
+ return strategy.operationsPOST(restconfURI, strategyAndTail.tail(), body);
}
@Override
.findModuleStatements("ietf-yang-library").iterator().next().localQNameModule().getRevision()
.map(Revision::toString).orElse(""))));
}
-
- private @NonNull InstanceIdentifierContext bindRequestPath(final @NonNull ApiPath identifier) {
- return bindRequestPath(localStrategy(), identifier);
- }
-
- private @NonNull InstanceIdentifierContext bindRequestPath(final @NonNull MdsalRestconfStrategy strategy,
- final @NonNull ApiPath identifier) {
- // FIXME: DatabindContext looks like it should be internal
- return InstanceIdentifierContext.ofApiPath(identifier, strategy.databind(), mountPointService);
- }
-
- private @NonNull InstanceIdentifierContext bindRequestRoot() {
- return InstanceIdentifierContext.ofLocalRoot(localStrategy().databind());
- }
-
- private @NonNull ResourceRequest bindResourceRequest(final InstanceIdentifierContext reqPath,
- final ResourceBody body) {
- final var putPath = new DataPutPath(reqPath.databind(), reqPath.inference(), reqPath.getInstanceIdentifier());
- return new ResourceRequest(getRestconfStrategy(putPath.databind(), reqPath.getMountPoint()), putPath.instance(),
- body.toNormalizedNode(putPath));
- }
-
- @VisibleForTesting
- @NonNull RestconfStrategy getRestconfStrategy(final DatabindContext databind,
- final @Nullable DOMMountPoint mountPoint) {
- if (mountPoint == null) {
- return localStrategy(databind);
- }
- return forMountPoint(databind, mountPoint);
- }
-
- private static @NonNull RestconfStrategy forMountPoint(final DatabindContext databind,
- final DOMMountPoint mountPoint) {
- final var ret = RestconfStrategy.forMountPoint(databind, mountPoint);
- if (ret == null) {
- final var mountId = mountPoint.getIdentifier();
- LOG.warn("Mount point {} does not expose a suitable access interface", mountId);
- throw new RestconfDocumentedException("Could not find a supported access interface in mount point",
- ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, mountId);
- }
- return ret;
- }
}
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.ApiPath.ListInstance;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.nb.rfc8040.Insert.PointNormalizer;
import org.opendaylight.restconf.server.api.DatabindContext;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath.Rpc;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
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.model.api.LeafListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
-import org.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.IdentityEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.InputEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
/**
* Deserializer for {@link String} to {@link YangInstanceIdentifier} for restconf.
*/
public final class ApiPathNormalizer implements PointNormalizer {
- public static final class Result {
- public final @NonNull YangInstanceIdentifier path;
- public final @NonNull SchemaInferenceStack stack;
- public final @NonNull SchemaNode node;
-
- Result(final EffectiveModelContext modelContext) {
- path = YangInstanceIdentifier.of();
- stack = SchemaInferenceStack.of(modelContext);
- node = requireNonNull(modelContext);
+ @NonNullByDefault
+ public sealed interface Path {
+
+ Inference inference();
+ }
+
+ @NonNullByDefault
+ public sealed interface InstanceReference extends Path {
+
+ YangInstanceIdentifier instance();
+ }
+
+ @NonNullByDefault
+ public record DataPath(Inference inference, YangInstanceIdentifier instance, DataSchemaContext schema)
+ implements InstanceReference {
+ public DataPath {
+ requireNonNull(inference);
+ requireNonNull(instance);
+ requireNonNull(schema);
}
+ }
- Result(final EffectiveModelContext modelContext, final QName qname) {
- // Legacy behavior: RPCs do not really have a YangInstanceIdentifier, but the rest of the code expects it
- path = YangInstanceIdentifier.of(qname);
- stack = SchemaInferenceStack.of(modelContext);
+ @NonNullByDefault
+ public sealed interface OperationPath extends Path {
- final var stmt = stack.enterSchemaTree(qname);
- verify(stmt instanceof RpcDefinition, "Unexpected statement %s", stmt);
- node = (RpcDefinition) stmt;
+ InputEffectiveStatement inputStatement();
+
+ record Action(Inference inference, YangInstanceIdentifier instance, ActionEffectiveStatement action)
+ implements OperationPath, InstanceReference {
+ public Action {
+ requireNonNull(inference);
+ requireNonNull(action);
+ requireNonNull(instance);
+ }
+
+ @Override
+ public InputEffectiveStatement inputStatement() {
+ return action.input();
+ }
}
- Result(final List<PathArgument> steps, final SchemaInferenceStack stack, final SchemaNode node) {
- path = YangInstanceIdentifier.of(steps);
- this.stack = requireNonNull(stack);
- this.node = requireNonNull(node);
+ record Rpc(Inference inference, RpcEffectiveStatement rpc) implements OperationPath {
+ public Rpc {
+ requireNonNull(inference);
+ requireNonNull(rpc);
+ }
+
+ @Override
+ public InputEffectiveStatement inputStatement() {
+ return rpc.input();
+ }
}
}
instanceIdentifierCodec = new ApiPathInstanceIdentifierCodec(databind);
}
- @Override
- public PathArgument normalizePoint(final ApiPath value) {
- return normalizePath(value).path.getLastPathArgument();
- }
-
- // FIXME: NETCONF-818: this method really needs to report an Inference and optionally a YangInstanceIdentifier
- // - we need the inference for discerning the correct context
- // - RPCs do not have a YangInstanceIdentifier
- // - Actions always have a YangInstanceIdentifier, but it points to their parent
- // - we need to discern the cases RPC invocation, Action invocation and data tree access quickly
- //
- // All of this really is an utter mess because we end up calling into this code from various places which,
- // for example, should not allow RPCs to be valid targets
- public @NonNull Result normalizePath(final ApiPath apiPath) {
+ public @NonNull Path normalizePath(final ApiPath apiPath) {
final var it = apiPath.steps().iterator();
if (!it.hasNext()) {
- return new Result(modelContext);
+ return new DataPath(Inference.ofDataTreePath(modelContext), YangInstanceIdentifier.of(),
+ databind.schemaTree().getRoot());
}
// First step is somewhat special:
final var optRpc = modelContext.findModuleStatement(namespace).orElseThrow()
.findSchemaTreeNode(RpcEffectiveStatement.class, qname);
if (optRpc.isPresent()) {
+ final var rpc = optRpc.orElseThrow();
+
// We have found an RPC match,
if (it.hasNext()) {
throw new RestconfDocumentedException("First step in the path resolves to RPC '" + qname + "' and "
+ "therefore it must not contain key values", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
}
- return new Result(modelContext, optRpc.orElseThrow().argument());
+ final var stack = SchemaInferenceStack.of(modelContext);
+ final var stmt = stack.enterSchemaTree(rpc.argument());
+ verify(rpc.equals(stmt), "Expecting %s, inferred %s", rpc, stmt);
+ return new OperationPath.Rpc(stack.toInference(), rpc);
}
final var stack = SchemaInferenceStack.of(modelContext);
final var path = new ArrayList<PathArgument>();
- final SchemaNode node;
-
DataSchemaContext parentNode = databind.schemaTree().getRoot();
while (true) {
final var parentSchema = parentNode.dataSchemaNode();
if (parentSchema instanceof ActionNodeContainer actionParent) {
final var optAction = actionParent.findAction(qname);
if (optAction.isPresent()) {
+ final var action = optAction.orElseThrow();
+
if (it.hasNext()) {
throw new RestconfDocumentedException("Request path resolves to action '" + qname + "' and "
+ "therefore it must not continue past it", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
}
- // Legacy behavior: Action's path should not include its path, but the rest of the code expects it
- path.add(new NodeIdentifier(qname));
- stack.enterSchemaTree(qname);
- node = optAction.orElseThrow();
- break;
+ final var stmt = stack.enterSchemaTree(qname);
+ final var actionStmt = action.asEffectiveStatement();
+ verify(actionStmt.equals(stmt), "Expecting %s, inferred %s", actionStmt, stmt);
+
+ return new OperationPath.Action(stack.toInference(), YangInstanceIdentifier.of(path), actionStmt);
}
}
path.add(pathArg);
if (!it.hasNext()) {
- node = childNode.dataSchemaNode();
- break;
+ return new DataPath(stack.toInference(), YangInstanceIdentifier.of(path), childNode);
}
parentNode = childNode;
qname = step.identifier().bindTo(namespace);
}
+ }
+
+ public @NonNull DataPath normalizeDataPath(final ApiPath apiPath) {
+ final var path = normalizePath(apiPath);
+ if (path instanceof DataPath dataPath) {
+ return dataPath;
+ }
+ throw new RestconfDocumentedException("Point '" + apiPath + "' resolves to non-data " + path,
+ ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
+ }
+
+ @Override
+ public PathArgument normalizePoint(final ApiPath value) {
+ final var path = normalizePath(value);
+ if (path instanceof DataPath dataPath) {
+ final var lastArg = dataPath.instance().getLastPathArgument();
+ if (lastArg != null) {
+ return lastArg;
+ }
+ throw new IllegalArgumentException("Point '" + value + "' resolves to an empty path");
+ }
+ throw new IllegalArgumentException("Point '" + value + "' resolves to non-data " + path);
+ }
+
+ public @NonNull Rpc normalizeRpcPath(final ApiPath apiPath) {
+ final var steps = apiPath.steps();
+ return switch (steps.size()) {
+ case 0 -> throw new RestconfDocumentedException("RPC name must be present", ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING);
+ case 1 -> normalizeRpcPath(steps.get(0));
+ default -> throw new RestconfDocumentedException(apiPath + " does not refer to an RPC", ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING);
+ };
+ }
+
+ public @NonNull Rpc normalizeRpcPath(final ApiPath.Step step) {
+ final var firstModule = step.module();
+ if (firstModule == null) {
+ throw new RestconfDocumentedException(
+ "First member must use namespace-qualified form, '" + step.identifier() + "' does not",
+ ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE);
+ }
- return new Result(path, stack, node);
+ final var namespace = resolveNamespace(firstModule);
+ final var qname = step.identifier().bindTo(namespace);
+ final var stack = SchemaInferenceStack.of(modelContext);
+ final SchemaTreeEffectiveStatement<?> stmt;
+ try {
+ stmt = stack.enterSchemaTree(qname);
+ } catch (IllegalArgumentException e) {
+ throw new RestconfDocumentedException(qname + " does not refer to an RPC", ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING, e);
+ }
+ if (stmt instanceof RpcEffectiveStatement rpc) {
+ return new Rpc(stack.toInference(), rpc);
+ }
+ throw new RestconfDocumentedException(qname + " does not refer to an RPC", ErrorType.PROTOCOL,
+ ErrorTag.DATA_MISSING);
+ }
+
+ public @NonNull InstanceReference normalizeDataOrActionPath(final ApiPath apiPath) {
+
+
+ // FIXME: optimize this
+ final var path = normalizePath(apiPath);
+ if (path instanceof DataPath dataPath) {
+ return dataPath;
+ }
+ if (path instanceof OperationPath.Action actionPath) {
+ return actionPath;
+ }
+ throw new RestconfDocumentedException("Unexpected path " + path, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING);
}
private NodeIdentifierWithPredicates prepareNodeWithPredicates(final SchemaInferenceStack stack, final QName qname,
import org.mockito.junit.jupiter.MockitoExtension;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
.getService(DOMSchemaService.class);
doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
doNothing().when(tx).delete(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
.getService(DOMSchemaService.class);
doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
// response must contain all child nodes from config and operational containers merged in one container
import org.mockito.junit.jupiter.MockitoExtension;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
.getService(DOMSchemaService.class);
doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
assertNull(assertEntity(204, ar -> restconf.dataXmlPUT(
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.api.DOMYangTextSourceProvider;
doReturn(Optional.of(FixedDOMSchemaService.of(MODEL_CONTEXT_ON_MOUNT_POINT))).when(mountPoint)
.getService(DOMSchemaService.class);
doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(MOUNT_IID);
+ doReturn(Optional.of(mountPointService)).when(mountPoint).getService(DOMMountPointService.class);
doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
}
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.yang.gen.v1.module._1.rev140101.Module1Data;
import org.opendaylight.yang.gen.v1.module._2.rev140102.Module2Data;
private void mockMountPoint() {
doReturn(Optional.of(FixedDOMSchemaService.of(MODEL_CONTEXT))).when(mountPoint)
.getService(DOMSchemaService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
+ doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(any());
}
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMNotificationService;
import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
import org.opendaylight.mdsal.dom.api.DOMRpcResult;
doReturn(Optional.of(FixedDOMSchemaService.of(MODEL_CONTEXT))).when(mountPoint)
.getService(DOMSchemaService.class);
doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of(
doReturn(Optional.of(FixedDOMSchemaService.of(MODEL_CONTEXT))).when(mountPoint)
.getService(DOMSchemaService.class);
doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of(
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMMountPoint;
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.common.patch.PatchContext;
import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
import org.opendaylight.restconf.server.api.DataPatchPath;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
abstract class AbstractPatchBodyTest extends AbstractInstanceIdentifierTest {
private final Function<InputStream, PatchBody> bodyConstructor;
+ @Mock
+ private DOMDataBroker dataBroker;
@Mock
DOMMountPointService mountPointService;
@Mock
public final void before() {
doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(any(YangInstanceIdentifier.class));
doReturn(Optional.of(FixedDOMSchemaService.of(IID_SCHEMA))).when(mountPoint).getService(DOMSchemaService.class);
+ doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
}
@NonNull String mountPrefix() {
throw new AssertionError(e);
}
- final var iid = InstanceIdentifierContext.ofApiPath(apiPath, IID_DATABIND, mountPointService);
+ final var strategy = new MdsalRestconfStrategy(IID_DATABIND, dataBroker, null, null, null, mountPointService);
+ final var stratAndPath = strategy.resolveStrategyPath(apiPath);
try (var body = bodyConstructor.apply(stringInputStream(patchBody))) {
- return body.toPatchContext(new DataPatchPath(iid.databind(), iid.getInstanceIdentifier()));
+ return body.toPatchContext(new DataPatchPath(stratAndPath.strategy().databind(),
+ stratAndPath.path().instance()));
}
}
}
import org.junit.jupiter.api.function.Executable;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMMountPoint;
import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
import org.opendaylight.restconf.server.api.DataPutPath;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.yangtools.yang.common.ErrorTag;
private static DatabindContext DATABIND;
+ @Mock
+ DOMDataBroker dataBroker;
@Mock
DOMMountPointService mountPointService;
@Mock
throw new AssertionError(e);
}
+ final var strategy = new MdsalRestconfStrategy(DATABIND, dataBroker, null, null, null, mountPointService);
+ final var stratAndPath = strategy.resolveStrategyPath(apiPath);
+
try (var body = bodyConstructor.apply(stringInputStream(patchBody))) {
- final var context = InstanceIdentifierContext.ofApiPath(apiPath, DATABIND, mountPointService);
- return body.toNormalizedNode(
- new DataPutPath(context.databind(), context.inference(), context.getInstanceIdentifier()));
+ return body.toNormalizedNode(new DataPutPath(stratAndPath.strategy().databind(),
+ stratAndPath.path().inference(), stratAndPath.path().instance()));
}
}
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.Test;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
+import org.opendaylight.mdsal.dom.api.DOMDataBroker;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(any(YangInstanceIdentifier.class));
doReturn(Optional.of(FixedDOMSchemaService.of(IID_SCHEMA))).when(mountPoint)
.getService(DOMSchemaService.class);
+ doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
}
/**
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import javax.ws.rs.core.MultivaluedHashMap;
import org.opendaylight.restconf.api.query.WithDefaultsParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.nb.rfc8040.Insert;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
import org.opendaylight.restconf.nb.rfc8040.legacy.QueryParameters;
import org.opendaylight.restconf.nb.rfc8040.utils.parser.WriterFieldsTranslator;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
import org.opendaylight.yangtools.yang.model.api.stmt.ContainerEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class QueryParamsTest {
doReturn(containerChild).when(containerChildSchema).getQName();
doReturn(containerChildSchema).when(containerSchema).dataChildByName(containerChild);
- final var module = mock(ModuleEffectiveStatement.class);
- doReturn(Optional.of(containerSchema)).when(module).findSchemaTreeNode(containerQName);
- final var context = mock(EffectiveModelContext.class);
- doReturn(Map.of(containerQName.getModule(), module)).when(context).getModuleStatements();
-
- final var stack = SchemaInferenceStack.of(context);
- stack.enterSchemaTree(containerQName);
- final var iid = InstanceIdentifierContext.ofStack(DatabindContext.ofModel(context), stack);
-
- final var queryParameters = QueryParameters.ofFields(params, WriterFieldsTranslator.translate(
- iid.databind().modelContext(), (DataSchemaNode) iid.getSchemaNode(), paramsFields));
+ final var queryParameters = QueryParameters.of(params, WriterFieldsTranslator.translate(
+ mock(EffectiveModelContext.class), DataSchemaContext.of(containerSchema), paramsFields));
final var fields = queryParameters.fields();
assertNotNull(fields);
assertEquals(1, fields.size());
+++ /dev/null
-/*
- * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.restconf.nb.rfc8040.legacy;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.text.ParseException;
-import org.eclipse.jdt.annotation.Nullable;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.opendaylight.mdsal.dom.api.DOMMountPoint;
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.mdsal.dom.api.DOMSchemaService;
-import org.opendaylight.mdsal.dom.broker.DOMMountPointServiceImpl;
-import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
-import org.opendaylight.restconf.api.ApiPath;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.common.errors.RestconfError;
-import org.opendaylight.restconf.server.api.DatabindContext;
-import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
-import org.opendaylight.yangtools.yang.common.ErrorTag;
-import org.opendaylight.yangtools.yang.common.ErrorType;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
-
-class InstanceIdentifierContextTest {
- // mount point identifier
- private static final String MOUNT_POINT_IDENT = "mount-point:mount-container/point-number/yang-ext:mount";
-
- // test identifier + expected result
- private static final String TEST_IDENT =
- "parser-identifier:cont1/cont2/listTest/list-in-grouping=name/leaf-A.B";
-
- private static final String TEST_IDENT_RESULT = """
- /(parser:identifier?revision=2016-06-02)cont1/cont2/listTest/listTest/list-in-grouping/\
- list-in-grouping[{(parser:identifier?revision=2016-06-02)name=name}]/leaf-A.B""";
-
- // test identifier with nodes defined in other modules using augmentation + expected result
- private static final String TEST_IDENT_OTHERS =
- "parser-identifier-included:list-1=name,2016-06-02/parser-identifier:augment-leaf";
-
- private static final String TEST_IDENT_OTHERS_RESULT = """
- /(parser:identifier:included?revision=2016-06-02)list-1/list-1\
- [{(parser:identifier:included?revision=2016-06-02)name=name, \
- (parser:identifier:included?revision=2016-06-02)revision=2016-06-02}]\
- /(parser:identifier?revision=2016-06-02)augment-leaf""";
-
- // invalid test identifier
- private static final String INVALID_TEST_IDENT =
- "parser-identifier:cont2/listTest/list-in-grouping=name/leaf-A.B";
-
- private static final String INVOKE_RPC = "invoke-rpc-module:rpc-test";
- private static final String INVOKE_ACTION = "example-actions:interfaces/interface=eth0/reset";
-
- // schema context with test modules
- private static final EffectiveModelContext MODEL_CONTEXT =
- YangParserTestUtils.parseYangResourceDirectory("/parser-identifier");
- // contains the same modules but it is different object (it can be compared with equals)
- // FIXME: we really should use a different context of mount point
- private static final EffectiveModelContext MODEL_CONTEXT_ON_MOUNT_POINT =
- YangParserTestUtils.parseYangResourceDirectory("/parser-identifier");
-
- // mount point and mount point service
- private final DOMMountPointService mountPointService = new DOMMountPointServiceImpl();
- private DOMMountPoint mountPoint;
-
- @BeforeEach
- void beforeEach() {
- // create and register mount point
- final var mountPointId = YangInstanceIdentifier.of(
- QName.create("mount:point", "2016-06-02", "mount-container"),
- QName.create("mount:point", "2016-06-02", "point-number"));
-
- mountPoint = mountPointService.createMountPoint(mountPointId)
- .addService(DOMSchemaService.class, FixedDOMSchemaService.of(MODEL_CONTEXT_ON_MOUNT_POINT))
- .register()
- .getInstance();
- }
-
- /**
- * {@link ParserIdentifier#toInstanceIdentifier(String, SchemaContext)} tests.
- */
-
- /**
- * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when all nodes are defined
- * in one module.
- */
- @Test
- void toInstanceIdentifierTest() {
- final var context = assertContext(TEST_IDENT, MODEL_CONTEXT, null);
- assertEquals(TEST_IDENT_RESULT, context.getInstanceIdentifier().toString());
- }
-
- /**
- * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when nodes are defined in
- * multiple modules.
- */
- @Test
- void toInstanceIdentifierOtherModulesTest() {
- final var context = assertContext(TEST_IDENT_OTHERS, MODEL_CONTEXT, null);
- assertEquals(TEST_IDENT_OTHERS_RESULT, context.getInstanceIdentifier().toString());
- }
-
- /**
- * Positive test of creating {@code InstanceIdentifierContext} from identifier containing {@code yang-ext:mount}.
- */
- @Test
- void toInstanceIdentifierMountPointTest() {
- final var context = assertContext(MOUNT_POINT_IDENT + "/" + TEST_IDENT, MODEL_CONTEXT,
- mountPointService);
- assertEquals(TEST_IDENT_RESULT.toString(), context.getInstanceIdentifier().toString());
- assertEquals(mountPoint, context.getMountPoint());
- assertSame(MODEL_CONTEXT_ON_MOUNT_POINT, context.databind().modelContext());
- }
-
- /**
- * Negative test of creating <code>InstanceIdentifierContext</code> when <code>SchemaContext</code> is
- * <code>null</code>. Test fails expecting <code>NullPointerException</code>.
- */
- @Test
- void toInstanceIdentifierNullSchemaContextNegativeTest() {
- assertThrows(NullPointerException.class, () -> assertContext(TEST_IDENT, null, null));
- }
-
- /**
- * Api path can be empty. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
- */
- @Test
- void toInstanceIdentifierEmptyIdentifierTest() {
- final var context = assertContext("", MODEL_CONTEXT, null);
- assertEquals(YangInstanceIdentifier.of(), context.getInstanceIdentifier());
- }
-
- /**
- * Negative test with invalid test identifier. Test should fail with <code>RestconfDocumentedException</code>.
- */
- @Test
- void toInstanceIdentifierInvalidIdentifierNegativeTest() {
- final var error = assertError(INVALID_TEST_IDENT, MODEL_CONTEXT, null);
- assertEquals("Schema for '(parser:identifier?revision=2016-06-02)cont2' not found", error.getErrorMessage());
- assertEquals(ErrorType.PROTOCOL, error.getErrorType());
- assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
- }
-
- /**
- * Negative test when identifier contains {@code yang-ext:mount} but identifier part is not valid. Test
- * should fail with {@link RestconfDocumentedException}.
- */
- @Test
- void toInstanceIdentifierMountPointInvalidIdentifierNegativeTest() {
- final var error = assertError("mount-point:point-number/yang-ext:mount", MODEL_CONTEXT, mountPointService);
- assertEquals("Schema for '(mount:point?revision=2016-06-02)point-number' not found", error.getErrorMessage());
- assertEquals(ErrorType.PROTOCOL, error.getErrorType());
- assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
- }
-
- /**
- * Negative test when <code>DOMMountPoint</code> cannot be found. Test is expected to fail with
- * <code>RestconfDocumentedException</code> error type, error tag and error status code are
- * compared to expected values.
- */
- @Test
- void toInstanceIdentifierMissingMountPointNegativeTest() {
- final var error = assertError("yang-ext:mount", MODEL_CONTEXT, mountPointService);
- assertEquals("Mount point '' does not exist", error.getErrorMessage());
- assertEquals(ErrorType.PROTOCOL, error.getErrorType());
- assertEquals(ErrorTags.RESOURCE_DENIED_TRANSPORT, error.getErrorTag());
- }
-
- /**
- * Negative test when <code>{@link DOMMountPointService}</code> is absent. Test is expected to fail with
- * <code>RestconfDocumentedException</code> error type, error tag and error status code are
- * compared to expected values.
- */
- @Test
- void toInstanceIdentifierMissingMountPointServiceNegativeTest() {
- final var error = assertError("yang-ext:mount", MODEL_CONTEXT, null);
- assertEquals("Mount point service is not available", error.getErrorMessage());
- assertEquals(ErrorType.APPLICATION, error.getErrorType());
- assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
- }
-
- /**
- * Test invoke RPC. Verify if RPC schema node was found.
- */
- @Test
- void invokeRpcTest() throws Exception {
- final var result = assertContext(INVOKE_RPC, MODEL_CONTEXT, null);
-
- // RPC schema node
- final QName rpcQName = result.getSchemaNode().getQName();
- assertEquals("invoke:rpc:module", rpcQName.getModule().getNamespace().toString());
- assertEquals("rpc-test", rpcQName.getLocalName());
-
- // other fields
- assertEquals(assertPath(INVOKE_RPC), result.getInstanceIdentifier());
- assertEquals(null, result.getMountPoint());
- assertSame(MODEL_CONTEXT, result.databind().modelContext());
- }
-
- /**
- * Test invoke RPC on mount point. Verify if RPC schema node was found.
- */
- @Test
- void invokeRpcOnMountPointTest() throws Exception {
- final var result = assertContext(MOUNT_POINT_IDENT + "/" + INVOKE_RPC, MODEL_CONTEXT, mountPointService);
-
- // RPC schema node
- final var rpcQName = result.getSchemaNode().getQName();
- assertEquals("invoke:rpc:module", rpcQName.getModule().getNamespace().toString());
- assertEquals("rpc-test", rpcQName.getLocalName());
-
- // other fields
- assertEquals(assertPath(INVOKE_RPC), result.getInstanceIdentifier());
- assertEquals(mountPoint, result.getMountPoint());
- assertSame(MODEL_CONTEXT_ON_MOUNT_POINT, result.databind().modelContext());
- }
-
- /**
- * Test Action. Verify if Action schema node was found.
- */
- @Test
- void invokeActionTest() throws Exception {
- final var result = assertContext(INVOKE_ACTION, MODEL_CONTEXT, null);
-
- // Action schema node
- final var actionQName = result.getSchemaNode().getQName();
- assertEquals("https://example.com/ns/example-actions", actionQName.getModule().getNamespace().toString());
- assertEquals("reset", actionQName.getLocalName());
-
- // other fields
- assertEquals(assertPath(INVOKE_ACTION), result.getInstanceIdentifier());
- assertNull(result.getMountPoint());
- assertSame(MODEL_CONTEXT, result.databind().modelContext());
- }
-
- /**
- * Test invoke Action on mount point. Verify if Action schema node was found.
- */
- @Test
- void invokeActionOnMountPointTest() throws Exception {
- final var result = assertContext(MOUNT_POINT_IDENT + "/" + INVOKE_ACTION, MODEL_CONTEXT,
- mountPointService);
-
- // Action schema node
- final var actionQName = result.getSchemaNode().getQName();
- assertEquals("https://example.com/ns/example-actions", actionQName.getModule().getNamespace().toString());
- assertEquals("reset", actionQName.getLocalName());
-
- // other fields
- assertEquals(assertPath(INVOKE_ACTION), result.getInstanceIdentifier());
- assertEquals(mountPoint, result.getMountPoint());
- assertSame(MODEL_CONTEXT_ON_MOUNT_POINT, result.databind().modelContext());
- }
-
- private static InstanceIdentifierContext assertContext(final String identifier,
- final EffectiveModelContext schemaContext, final @Nullable DOMMountPointService mountPointService) {
- return InstanceIdentifierContext.ofApiPath(assertApiPath(identifier), DatabindContext.ofModel(schemaContext),
- mountPointService);
- }
-
- private static RestconfError assertError(final String identifier, final EffectiveModelContext schemaContext,
- final @Nullable DOMMountPointService mountPointService) {
- final var apiPath = assertApiPath(identifier);
- final var databind = DatabindContext.ofModel(schemaContext);
- final var ex = assertThrows(RestconfDocumentedException.class,
- () -> InstanceIdentifierContext.ofApiPath(apiPath, databind, mountPointService));
- final var errors = ex.getErrors();
- assertEquals(1, errors.size());
- return errors.get(0);
- }
-
- private static YangInstanceIdentifier assertPath(final String path) {
- return new ApiPathNormalizer(DatabindContext.ofModel(MODEL_CONTEXT)).normalizePath(assertApiPath(path)).path;
- }
-
- private static ApiPath assertApiPath(final String str) {
- try {
- return ApiPath.parseUrl(str);
- } catch (ParseException e) {
- throw new AssertionError(e);
- }
- }
-}
import javax.ws.rs.core.UriInfo;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
+import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.opendaylight.restconf.api.query.ContentParam;
import org.opendaylight.restconf.common.patch.PatchEntity;
import org.opendaylight.restconf.common.patch.PatchStatusContext;
import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
+import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
new NodeIdentifier(QName.create("ns", "2016-02-28", "container"));
@Mock
- EffectiveModelContext mockSchemaContext;
+ private EffectiveModelContext mockSchemaContext;
@Mock
private UriInfo uriInfo;
+ private DatabindContext mockDatabind;
+
+ @Before
+ public void initMockDatabind() {
+ mockDatabind = DatabindContext.ofModel(mockSchemaContext);
+ }
+
+ abstract @NonNull RestconfStrategy newStrategy(DatabindContext databind);
+
+ final @NonNull RestconfStrategy jukeboxStrategy() {
+ return newStrategy(JUKEBOX_DATABIND);
+ }
+
+ final @NonNull RestconfStrategy mockStrategy() {
+ return newStrategy(mockDatabind);
+ }
+
/**
* Test of successful DELETE operation.
*/
*/
package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateTrueFluentFuture;
import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.dom.api.DOMActionService;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMMountPoint;
+import org.opendaylight.mdsal.dom.api.DOMMountPointService;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMSchemaService;
+import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
+import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
+import org.opendaylight.restconf.api.ApiPath;
import org.opendaylight.restconf.api.query.ContentParam;
import org.opendaylight.restconf.api.query.WithDefaultsParam;
+import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.patch.PatchStatusContext;
+import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy.StrategyAndTail;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
@Mock
private DOMDataTreeReadWriteTransaction readWrite;
@Mock
- private DOMDataBroker mockDataBroker;
+ private DOMDataBroker dataBroker;
@Mock
private DOMDataTreeReadTransaction read;
@Mock
private DOMRpcService rpcService;
-
- private DatabindContext mockDatabind;
+ @Mock
+ private DOMSchemaService schemaService;
+ @Mock
+ private DOMMountPointService mountPointService;
+ @Mock
+ private DOMMountPoint mountPoint;
+ @Mock
+ private NetconfDataTreeService netconfService;
@Before
public void before() {
doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
- doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
- mockDatabind = DatabindContext.ofModel(mockSchemaContext);
+ doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
+ }
+
+ @Override
+ RestconfStrategy newStrategy(final DatabindContext databind) {
+ return new MdsalRestconfStrategy(databind, dataBroker, rpcService, null, null, mountPointService);
+ }
+
+ private @NonNull RestconfStrategy modulesStrategy() {
+ return newStrategy(MODULES_DATABIND);
}
@Override
// assert that data to delete exists
when(readWrite.exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of()))
.thenReturn(immediateTrueFluentFuture());
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
// assert that data to delete does NOT exist
when(readWrite.exists(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of()))
.thenReturn(immediateFalseFluentFuture());
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, entryNode);
doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doReturn(immediateFailedFluentFuture(domException)).when(readWrite).commit();
doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Test
public void testPutContainerData() {
- doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFalseFluentFuture()).when(read).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
- new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null)
- .putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
+ jukeboxStrategy().putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
verify(read).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
verify(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
}
@Test
public void testPutLeafData() {
- doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFalseFluentFuture()).when(read).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF);
doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
- new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null).putData(GAP_IID, GAP_LEAF, null);
+ jukeboxStrategy().putData(GAP_IID, GAP_LEAF, null);
verify(read).exists(LogicalDatastoreType.CONFIGURATION, GAP_IID);
verify(readWrite).put(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF);
}
@Test
public void testPutListData() {
- doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFalseFluentFuture())
.when(read).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS);
doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
- new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null)
- .putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
+ jukeboxStrategy().putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
verify(read).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
verify(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS);
}
doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID);
doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX);
doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
RestconfStrategy testPatchContainerDataStrategy() {
- doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
+ doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
RestconfStrategy testPatchLeafDataStrategy() {
- doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
+ doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
RestconfStrategy testPatchListDataStrategy() {
- doReturn(readWrite).when(mockDataBroker).newReadWriteTransaction();
+ doReturn(readWrite).when(dataBroker).newReadWriteTransaction();
doReturn(CommitInfo.emptyFluentFuture()).when(readWrite).commit();
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
RestconfStrategy testPatchDataReplaceMergeAndRemoveStrategy() {
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, PLAYER_IID);
doReturn(immediateTrueFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION,
CREATE_AND_DELETE_TARGET);
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
RestconfStrategy testPatchMergePutContainerStrategy() {
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
RestconfStrategy deleteNonexistentDataTestStrategy() {
doReturn(immediateFalseFluentFuture()).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION,
CREATE_AND_DELETE_TARGET);
- return new MdsalRestconfStrategy(JUKEBOX_DATABIND, mockDataBroker, null, null);
+ return jukeboxStrategy();
}
@Override
@Override
RestconfStrategy readDataConfigTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, PATH);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readAllHavingOnlyConfigTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, PATH);
doReturn(immediateFluentFuture(Optional.empty())).when(read)
.read(LogicalDatastoreType.OPERATIONAL, PATH);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readAllHavingOnlyNonConfigTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, PATH_2);
doReturn(immediateFluentFuture(Optional.empty())).when(read)
.read(LogicalDatastoreType.CONFIGURATION, PATH_2);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readDataNonConfigTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, PATH_2);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readContainerDataAllTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, PATH);
doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, PATH);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readContainerDataConfigNoValueOfContentTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, PATH);
doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, PATH);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readListDataAllTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(LIST_DATA))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, PATH_3);
doReturn(immediateFluentFuture(Optional.of(LIST_DATA_2))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, PATH_3);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readOrderedListDataAllTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_1))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, PATH_3);
doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_2))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, PATH_3);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readUnkeyedListDataAllTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_1))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, PATH_3);
doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_2))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, PATH_3);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readLeafListDataAllTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(LEAF_SET_NODE_1))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, LEAF_SET_NODE_PATH);
doReturn(immediateFluentFuture(Optional.of(LEAF_SET_NODE_2))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, LEAF_SET_NODE_PATH);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readOrderedLeafListDataAllTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(ORDERED_LEAF_SET_NODE_1))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, LEAF_SET_NODE_PATH);
doReturn(immediateFluentFuture(Optional.of(ORDERED_LEAF_SET_NODE_2))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, LEAF_SET_NODE_PATH);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readDataWrongPathOrNoContentTestStrategy() {
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.empty())).when(read).read(LogicalDatastoreType.CONFIGURATION, PATH_2);
- return new MdsalRestconfStrategy(mockDatabind, mockDataBroker, null, null);
+ return mockStrategy();
}
@Test
.withChild(ImmutableNodes.leafNode(QName.create(BASE, "exampleLeaf"), "i am leaf"))
.build();
final var path = YangInstanceIdentifier.of(CONT_QNAME);
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(data))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, path);
doReturn(immediateFluentFuture(Optional.of(data))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, path);
- assertEquals(data, new MdsalRestconfStrategy(MODULES_DATABIND, mockDataBroker, null, null)
- .readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
+ assertEquals(data, modulesStrategy().readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
}
@Test
.build())
.build();
final var path = YangInstanceIdentifier.of(CONT_QNAME);
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(data))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, path);
doReturn(immediateFluentFuture(Optional.of(data))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, path);
- assertEquals(data, new MdsalRestconfStrategy(MODULES_DATABIND, mockDataBroker, null, null)
- .readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
+ assertEquals(data, modulesStrategy().readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
}
@Test
.build())
.build();
final var path = YangInstanceIdentifier.of(CONT_QNAME);
- doReturn(read).when(mockDataBroker).newReadOnlyTransaction();
+ doReturn(read).when(dataBroker).newReadOnlyTransaction();
doReturn(immediateFluentFuture(Optional.of(content))).when(read)
.read(LogicalDatastoreType.CONFIGURATION, path);
doReturn(immediateFluentFuture(Optional.of(content))).when(read)
.read(LogicalDatastoreType.OPERATIONAL, path);
- assertEquals(content, new MdsalRestconfStrategy(MODULES_DATABIND, mockDataBroker, null, null)
- .readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
+ assertEquals(content, modulesStrategy().readData(ContentParam.ALL, path, WithDefaultsParam.TRIM));
+ }
+
+ @Test
+ public void testGetRestconfStrategyLocal() {
+ final var strategy = jukeboxStrategy();
+ assertEquals(new StrategyAndTail(strategy, ApiPath.empty()), strategy.resolveStrategy(ApiPath.empty()));
+ }
+
+ @Test
+ public void testGetRestconfStrategyMountDataBroker() throws Exception {
+ doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
+ doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
+ doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
+ .getService(DOMSchemaService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of());
+
+ final var strategy = jukeboxStrategy();
+ final var result = strategy.resolveStrategy(ApiPath.parse("yang-ext:mount"));
+ assertEquals(ApiPath.empty(), result.tail());
+ assertNotSame(strategy, assertInstanceOf(MdsalRestconfStrategy.class, result.strategy()));
+ }
+
+ @Test
+ public void testGetRestconfStrategyMountNetconfService() throws Exception {
+ doReturn(Optional.of(netconfService)).when(mountPoint).getService(NetconfDataTreeService.class);
+ doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
+ .getService(DOMSchemaService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of());
+
+ final var strategy = jukeboxStrategy();
+ final var result = strategy.resolveStrategy(ApiPath.parse("yang-ext:mount"));
+ assertEquals(ApiPath.empty(), result.tail());
+ assertInstanceOf(NetconfRestconfStrategy.class, result.strategy());
+ }
+
+ @Test
+ public void testGetRestconfStrategyMountNone() throws Exception {
+ doReturn(JUKEBOX_IID).when(mountPoint).getIdentifier();
+ doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMDataBroker.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMMountPointService.class);
+ doReturn(Optional.empty()).when(mountPoint).getService(DOMActionService.class);
+ doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
+ doReturn(Optional.of(FixedDOMSchemaService.of(JUKEBOX_SCHEMA))).when(mountPoint)
+ .getService(DOMSchemaService.class);
+ doReturn(Optional.of(mountPoint)).when(mountPointService).getMountPoint(YangInstanceIdentifier.of());
+
+ final var strategy = jukeboxStrategy();
+ final var mountPath = ApiPath.parse("yang-ext:mount");
+
+ final var ex = assertThrows(RestconfDocumentedException.class, () -> strategy.resolveStrategy(mountPath));
+ final var errors = ex.getErrors();
+ assertEquals(1, errors.size());
+ final var error = errors.get(0);
+ assertEquals(ErrorType.APPLICATION, error.getErrorType());
+ assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
+ assertEquals("Could not find a supported access interface in mount point", error.getErrorMessage());
+ assertEquals(JUKEBOX_IID, error.getErrorPath());
}
}
@Mock
private NetconfDataTreeService netconfService;
- private DatabindContext mockDatabind;
-
@Before
public void before() {
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
.delete(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.of());
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).merge(any(), any(),
any(), any());
- mockDatabind = DatabindContext.ofModel(mockSchemaContext);
+ }
+
+ @Override
+ RestconfStrategy newStrategy(final DatabindContext databind) {
+ return new NetconfRestconfStrategy(databind, netconfService, null, null, null, null);
}
@Override
RestconfStrategy testDeleteDataStrategy() {
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
doReturn(Futures.immediateFailedFuture(new TransactionCommitFailedException(
"Commit of transaction " + this + " failed", new NetconfDocumentedException("id",
ErrorType.RPC, ErrorTag.DATA_MISSING, ErrorSeverity.ERROR)))).when(netconfService).commit();
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.create(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX, Optional.empty());
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).create(
LogicalDatastoreType.CONFIGURATION, node, entryNode, Optional.empty());
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
.create(any(), any(), any(), any());
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).discardChanges();
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).unlock();
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
RestconfStrategy testPatchContainerDataStrategy() {
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).merge(any(), any(),any(),
any());
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.merge(any(), any(), any(), any());
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService).commit();
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.merge(any(), any(),any(),any());
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Test
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX, Optional.empty());
- new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null)
- .putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
+ jukeboxStrategy().putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
verify(netconfService).lock();
verify(netconfService).getConfig(JUKEBOX_IID);
verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX,
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX, Optional.empty());
- new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null)
- .putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
+ jukeboxStrategy().putData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
verify(netconfService).getConfig(JUKEBOX_IID);
verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, EMPTY_JUKEBOX,
Optional.empty());
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
- new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null).putData(GAP_IID, GAP_LEAF, null);
+ jukeboxStrategy().putData(GAP_IID, GAP_LEAF, null);
verify(netconfService).getConfig(GAP_IID);
verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
}
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
- new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null).putData(GAP_IID, GAP_LEAF, null);
+ jukeboxStrategy().putData(GAP_IID, GAP_LEAF, null);
verify(netconfService).getConfig(GAP_IID);
verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, GAP_IID, GAP_LEAF, Optional.empty());
}
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS, Optional.empty());
- new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null)
- .putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
+ jukeboxStrategy().putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
verify(netconfService).getConfig(JUKEBOX_IID);
verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS,
Optional.empty());
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS, Optional.empty());
- new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null)
- .putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
+ jukeboxStrategy().putData(JUKEBOX_IID, JUKEBOX_WITH_BANDS, null);
verify(netconfService).getConfig(JUKEBOX_IID);
verify(netconfService).replace(LogicalDatastoreType.CONFIGURATION, JUKEBOX_IID, JUKEBOX_WITH_BANDS,
Optional.empty());
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
// FIXME: exact match
.replace(any(), any(), any(), any());
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
.create(LogicalDatastoreType.CONFIGURATION, PLAYER_IID, EMPTY_JUKEBOX, Optional.empty());
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.delete(LogicalDatastoreType.CONFIGURATION, CREATE_AND_DELETE_TARGET);
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
RestconfStrategy testPatchMergePutContainerStrategy() {
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
.when(netconfService).commit();
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult())).when(netconfService)
.delete(LogicalDatastoreType.CONFIGURATION, CREATE_AND_DELETE_TARGET);
- return new NetconfRestconfStrategy(JUKEBOX_DATABIND, netconfService, null, null);
+ return jukeboxStrategy();
}
@Override
@Override
RestconfStrategy readDataConfigTestStrategy() {
doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readAllHavingOnlyConfigTestStrategy() {
doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).get(PATH);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readAllHavingOnlyNonConfigTestStrategy() {
doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(netconfService).get(PATH_2);
doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(PATH_2);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readDataNonConfigTestStrategy() {
doReturn(immediateFluentFuture(Optional.of(DATA_2))).when(netconfService).get(PATH_2);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readContainerDataAllTestStrategy() {
doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(netconfService).get(PATH);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readContainerDataConfigNoValueOfContentTestStrategy() {
doReturn(immediateFluentFuture(Optional.of(DATA_3))).when(netconfService).getConfig(PATH);
doReturn(immediateFluentFuture(Optional.of(DATA_4))).when(netconfService).get(PATH);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readListDataAllTestStrategy() {
doReturn(immediateFluentFuture(Optional.of(LIST_DATA))).when(netconfService).get(PATH_3);
doReturn(immediateFluentFuture(Optional.of(LIST_DATA_2))).when(netconfService).getConfig(PATH_3);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readOrderedListDataAllTestStrategy() {
doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_1))).when(netconfService).get(PATH_3);
doReturn(immediateFluentFuture(Optional.of(ORDERED_MAP_NODE_2))).when(netconfService).getConfig(PATH_3);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readUnkeyedListDataAllTestStrategy() {
doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_1))).when(netconfService).get(PATH_3);
doReturn(immediateFluentFuture(Optional.of(UNKEYED_LIST_NODE_2))).when(netconfService).getConfig(PATH_3);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
.get(LEAF_SET_NODE_PATH);
doReturn(immediateFluentFuture(Optional.of(LEAF_SET_NODE_2))).when(netconfService)
.getConfig(LEAF_SET_NODE_PATH);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
.get(LEAF_SET_NODE_PATH);
doReturn(immediateFluentFuture(Optional.of(ORDERED_LEAF_SET_NODE_2))).when(netconfService)
.getConfig(LEAF_SET_NODE_PATH);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
@Override
RestconfStrategy readDataWrongPathOrNoContentTestStrategy() {
doReturn(immediateFluentFuture(Optional.empty())).when(netconfService).getConfig(PATH_2);
- return new NetconfRestconfStrategy(mockDatabind, netconfService, null, null);
+ return mockStrategy();
}
}
package org.opendaylight.restconf.nb.rfc8040.utils.parser;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.opendaylight.restconf.api.query.FieldsParam;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
-import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
-import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
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.XMLNamespace;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
public abstract class AbstractFieldsTranslatorTest<T> extends AbstractJukeboxTest {
private static final EffectiveModelContext TEST_SERVICES_SCHEMA =
YangParserTestUtils.parseYangResourceDirectory("/test-services");
- private static final DatabindContext TEST_SERVICES_DATABIND = DatabindContext.ofModel(TEST_SERVICES_SCHEMA);
private static final EffectiveModelContext FOO_SCHEMA =
YangParserTestUtils.parseYangResourceDirectory("/same-qname-nodes");
- private static final DatabindContext FOO_DATABIND = DatabindContext.ofModel(FOO_SCHEMA);
- private DataSchemaNode jukeboxSchemaNode;
- private DataSchemaNode testServices;
- private DataSchemaNode foo;
+ private DataSchemaContext jukeboxSchemaNode;
+ private DataSchemaContext testServices;
+ private DataSchemaContext foo;
// container augmented library
protected static final QName AUGMENTED_LIBRARY_Q_NAME = QName.create(Q_NAME_MODULE_AUGMENTED_JUKEBOX,
@Before
public void setUp() {
- jukeboxSchemaNode = assertInstanceOf(DataSchemaNode.class, InstanceIdentifierContext.ofStack(JUKEBOX_DATABIND,
- SchemaInferenceStack.ofDataTreePath(JUKEBOX_SCHEMA, JUKEBOX_QNAME)).getSchemaNode());
- testServices = assertInstanceOf(DataSchemaNode.class, InstanceIdentifierContext.ofStack(TEST_SERVICES_DATABIND,
- SchemaInferenceStack.ofDataTreePath(TEST_SERVICES_SCHEMA, TEST_DATA_Q_NAME)).getSchemaNode());
- foo = assertInstanceOf(DataSchemaNode.class, InstanceIdentifierContext.ofStack(FOO_DATABIND,
- SchemaInferenceStack.ofDataTreePath(FOO_SCHEMA, FOO_Q_NAME)).getSchemaNode());
+ jukeboxSchemaNode = DataSchemaContextTree.from(JUKEBOX_SCHEMA).getRoot().childByQName(JUKEBOX_QNAME);
+ assertNotNull(jukeboxSchemaNode);
+ testServices = DataSchemaContextTree.from(TEST_SERVICES_SCHEMA).getRoot().childByQName(TEST_DATA_Q_NAME);
+ assertNotNull(testServices);
+ foo = DataSchemaContextTree.from(FOO_SCHEMA).getRoot().childByQName(FOO_Q_NAME);
+ assertNotNull(foo);
}
protected abstract List<T> translateFields(@NonNull EffectiveModelContext modelContext,
- @NonNull DataSchemaNode schemaNode, @NonNull FieldsParam fields);
+ @NonNull DataSchemaContext startNode, @NonNull FieldsParam fields);
/**
* Test parse fields parameter containing only one child selected.
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
/**
public class NetconfFieldsTranslatorTest extends AbstractFieldsTranslatorTest<YangInstanceIdentifier> {
@Override
protected List<YangInstanceIdentifier> translateFields(final EffectiveModelContext modelContext,
- final DataSchemaNode schemaNode, final FieldsParam fields) {
- return NetconfFieldsTranslator.translate(modelContext, schemaNode, fields);
+ final DataSchemaContext startNode, final FieldsParam fields) {
+ return NetconfFieldsTranslator.translate(modelContext, startNode, fields);
}
@Override
import org.mockito.junit.MockitoJUnitRunner;
import org.opendaylight.restconf.api.query.FieldsParam;
import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
/**
public class WriterFieldsTranslatorTest extends AbstractFieldsTranslatorTest<Set<QName>> {
@Override
protected List<Set<QName>> translateFields(final EffectiveModelContext modelContext,
- final DataSchemaNode schemaNode, final FieldsParam fields) {
- return WriterFieldsTranslator.translate(modelContext, schemaNode, fields);
+ final DataSchemaContext startNode, final FieldsParam fields) {
+ return WriterFieldsTranslator.translate(modelContext, startNode, fields);
}
@Override
package org.opendaylight.restconf.nb.rfc8040.utils.parser;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.server.api.DatabindContext;
import org.opendaylight.restconf.server.spi.ApiPathNormalizer;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
private static YangInstanceIdentifier assertNormalized(final String str) {
try {
- return new ApiPathNormalizer(DATABIND).normalizePath(ApiPath.parse(str)).path;
+ return assertInstanceOf(DataPath.class, new ApiPathNormalizer(DATABIND).normalizePath(ApiPath.parse(str)))
+ .instance();
} catch (ParseException e) {
throw new AssertionError(e);
}
+++ /dev/null
-/*
- * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.restconf.server.mdsal;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.doReturn;
-
-import com.google.common.collect.ImmutableClassToInstanceMap;
-import java.util.Optional;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-import org.opendaylight.mdsal.dom.api.DOMActionService;
-import org.opendaylight.mdsal.dom.api.DOMDataBroker;
-import org.opendaylight.mdsal.dom.api.DOMMountPoint;
-import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
-import org.opendaylight.mdsal.dom.api.DOMSchemaService;
-import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
-import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
-import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
-import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
-import org.opendaylight.restconf.nb.rfc8040.rests.transactions.NetconfRestconfStrategy;
-import org.opendaylight.yangtools.yang.common.ErrorTag;
-import org.opendaylight.yangtools.yang.common.ErrorType;
-
-@ExtendWith(MockitoExtension.class)
-class MdsalRestconfServerTest extends AbstractJukeboxTest {
- @Mock
- private DOMMountPointService mountPointService;
- @Mock
- private DOMMountPoint mountPoint;
- @Mock
- private DOMDataBroker dataBroker;
- @Mock
- private NetconfDataTreeService netconfService;
- @Mock
- private DOMRpcService rpcService;
- @Mock
- private DOMActionService actionService;
- @Mock
- private DOMSchemaService schemaService;
-
- private MdsalRestconfServer server;
-
- @BeforeEach
- void before() {
- server = new MdsalRestconfServer(FixedDOMSchemaService.of(JUKEBOX_SCHEMA), dataBroker, rpcService,
- actionService, mountPointService);
- }
-
- @Test
- void testGetRestconfStrategyLocal() {
- assertInstanceOf(MdsalRestconfStrategy.class, server.getRestconfStrategy(JUKEBOX_DATABIND, null));
- }
-
- @Test
- void testGetRestconfStrategyMountDataBroker() {
- doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
- doReturn(Optional.of(dataBroker)).when(mountPoint).getService(DOMDataBroker.class);
- doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
- doReturn(Optional.of(schemaService)).when(mountPoint).getService(DOMSchemaService.class);
- doReturn(ImmutableClassToInstanceMap.of()).when(schemaService).getExtensions();
- assertInstanceOf(MdsalRestconfStrategy.class, server.getRestconfStrategy(JUKEBOX_DATABIND, mountPoint));
- }
-
- @Test
- void testGetRestconfStrategyMountNetconfService() {
- doReturn(Optional.of(netconfService)).when(mountPoint).getService(NetconfDataTreeService.class);
- doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
- doReturn(Optional.of(schemaService)).when(mountPoint).getService(DOMSchemaService.class);
- doReturn(ImmutableClassToInstanceMap.of()).when(schemaService).getExtensions();
- assertInstanceOf(NetconfRestconfStrategy.class, server.getRestconfStrategy(JUKEBOX_DATABIND, mountPoint));
- }
-
- @Test
- void testGetRestconfStrategyMountNone() {
- doReturn(JUKEBOX_IID).when(mountPoint).getIdentifier();
- doReturn(Optional.empty()).when(mountPoint).getService(NetconfDataTreeService.class);
- doReturn(Optional.empty()).when(mountPoint).getService(DOMDataBroker.class);
- doReturn(Optional.of(rpcService)).when(mountPoint).getService(DOMRpcService.class);
- doReturn(Optional.of(schemaService)).when(mountPoint).getService(DOMSchemaService.class);
- doReturn(ImmutableClassToInstanceMap.of()).when(schemaService).getExtensions();
- final var ex = assertThrows(RestconfDocumentedException.class,
- () -> server.getRestconfStrategy(JUKEBOX_DATABIND, mountPoint));
- final var errors = ex.getErrors();
- assertEquals(1, errors.size());
- final var error = errors.get(0);
- assertEquals(ErrorType.APPLICATION, error.getErrorType());
- assertEquals(ErrorTag.OPERATION_FAILED, error.getErrorTag());
- assertEquals("Could not find a supported access interface in mount point", error.getErrorMessage());
- assertEquals(JUKEBOX_IID, error.getErrorPath());
- }
-}
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.restconf.common.errors.RestconfError;
import org.opendaylight.restconf.server.api.DatabindContext;
-import org.opendaylight.restconf.server.spi.ApiPathNormalizer.Result;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.DataPath;
+import org.opendaylight.restconf.server.spi.ApiPathNormalizer.OperationPath.Action;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
*/
@Test
void deserializeContainerTest() {
- final var result = assertNormalizedPath("deserializer-test:contA").path.getPathArguments();
+ final var result = assertNormalizedPath("deserializer-test:contA").getPathArguments();
assertEquals(1, result.size());
assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
}
*/
@Test
void deserializeContainerWithLeafTest() {
- final var result = assertNormalizedPath("deserializer-test:contA/leaf-A").path.getPathArguments();
+ final var result = assertNormalizedPath("deserializer-test:contA/leaf-A").getPathArguments();
assertEquals(2, result.size());
assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "leaf-A")), result.get(1));
*/
@Test
void deserializeContainerWithListWithLeafListTest() {
- final var result = assertNormalizedPath("deserializer-test:contA/list-A=100/leaf-list-AA=instance").path
+ final var result = assertNormalizedPath("deserializer-test:contA/list-A=100/leaf-list-AA=instance")
.getPathArguments();
assertEquals(5, result.size());
*/
@Test
void deserializeContainerWithListWithActionTest() {
- final var result = assertNormalizedPath("example-actions:interfaces/interface=eth0/reset").path
- .getPathArguments();
- assertEquals(4, result.size());
- // container
- assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
- // list
+ final var result = assertNormalizedAction("example-actions:interfaces/interface=eth0/reset");
final var list = QName.create(ACTIONS_INTERFACES, "interface");
- assertEquals(NodeIdentifier.create(list), result.get(1));
- assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "eth0"), result.get(2));
- // action
- assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reset")), result.get(3));
+ assertEquals(YangInstanceIdentifier.builder()
+ // container
+ .node(ACTIONS_INTERFACES)
+ // list
+ .node(list)
+ .nodeWithKey(list, QName.create(list, "name"), "eth0")
+ .build(), result.instance());
+ assertEquals(QName.create(ACTIONS_INTERFACES, "reset"), result.action().argument());
}
/**
*/
@Test
void deserializeContainerWithChoiceSchemaNodeWithActionTest() {
- final var result = assertNormalizedPath("example-actions:interfaces/typeA-gigabyte/interface=eth0/reboot").path
- .getPathArguments();
- assertEquals(6, result.size());
-
- // container
- assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
- // choice
- assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "interface-type")), result.get(1));
- // container
- assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "typeA-gigabyte")), result.get(2));
-
- // list
+ final var result = assertNormalizedAction("example-actions:interfaces/typeA-gigabyte/interface=eth0/reboot");
final var list = QName.create(ACTIONS_INTERFACES, "interface");
- assertEquals(NodeIdentifier.create(list), result.get(3));
- assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "eth0"), result.get(4));
-
- // action QName
- assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reboot")), result.get(5));
+ assertEquals(YangInstanceIdentifier.builder()
+ // container
+ .node(ACTIONS_INTERFACES)
+ // choice
+ .node(QName.create(ACTIONS_INTERFACES, "interface-type"))
+ // container
+ .node(QName.create(ACTIONS_INTERFACES, "typeA-gigabyte"))
+ // list
+ .node(list)
+ .nodeWithKey(list, QName.create(list, "name"), "eth0")
+ .build(), result.instance());
+ assertEquals(QName.create(ACTIONS_INTERFACES, "reboot"), result.action().argument());
}
/**
*/
@Test
void deserializeContainerWithChoiceCaseSchemaNodeWithActionTest() {
- final var result = assertNormalizedPath("example-actions:interfaces/udp/reboot").path.getPathArguments();
- assertEquals(4, result.size());
- // container
- assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
- // choice
- assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "protocol")), result.get(1));
- // choice container
- assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "udp")), result.get(2));
- // action QName
- assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reboot")), result.get(3));
+ final var result = assertNormalizedAction("example-actions:interfaces/udp/reboot");
+ assertEquals(YangInstanceIdentifier.of(
+ // container
+ ACTIONS_INTERFACES,
+ // choice
+ QName.create(ACTIONS_INTERFACES, "protocol"),
+ // choice container
+ QName.create(ACTIONS_INTERFACES, "udp")),
+ result.instance());
}
/**
*/
@Test
void deserializeListWithNoKeysTest() {
- final var result = assertNormalizedPath("deserializer-test:list-no-key").path.getPathArguments();
+ final var result = assertNormalizedPath("deserializer-test:list-no-key").getPathArguments();
assertEquals(2, result.size());
final var list = QName.create("deserializer:test", "2016-06-06", "list-no-key");
assertEquals(NodeIdentifier.create(list), result.get(0));
*/
@Test
void deserializeListWithOneKeyTest() {
- final var result = assertNormalizedPath("deserializer-test:list-one-key=value").path.getPathArguments();
+ final var result = assertNormalizedPath("deserializer-test:list-one-key=value").getPathArguments();
assertEquals(2, result.size());
final QName list = QName.create("deserializer:test", "2016-06-06", "list-one-key");
assertEquals(NodeIdentifier.create(list), result.get(0));
QName.create(list, "number"), Uint8.valueOf(100),
QName.create(list, "enabled"), false);
- final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=value,100,false").path
+ final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=value,100,false")
.getPathArguments();
assertEquals(2, result.size());
assertEquals(NodeIdentifier.create(list), result.get(0));
*/
@Test
void deserializeLeafListTest() {
- final var result = assertNormalizedPath("deserializer-test:leaf-list-0=true").path.getPathArguments();
+ final var result = assertNormalizedPath("deserializer-test:leaf-list-0=true").getPathArguments();
assertEquals(2, result.size());
final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-0");
*/
@Test
void deserializeEmptyDataTest() {
- assertEquals(YangInstanceIdentifier.of(), assertNormalizedPath("").path);
+ assertEquals(YangInstanceIdentifier.of(), assertNormalizedPath(""));
}
/**
QName.create(list, "enabled"), false);
final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=%3Afoo,1,false/string-value")
- .path.getPathArguments();
+ .getPathArguments();
assertEquals(3, result.size());
// list
assertEquals(NodeIdentifier.create(list), result.get(0));
@Test
void percentEncodedKeyEndsWithNoPercentEncodedChars() {
final var URI = "deserializer-test:list-multiple-keys=%3Afoo,1,true";
- final var result = assertNormalizedPath(URI).path;
+ final var result = assertNormalizedPath(URI);
final var resultListKeys = assertInstanceOf(NodeIdentifierWithPredicates.class, result.getLastPathArgument())
.entrySet().iterator();
QName.create(list, "number"), Uint8.ZERO,
QName.create(list, "enabled"), true);
- final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=,0,true").path.getPathArguments();
+ final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=,0,true").getPathArguments();
assertEquals(2, result.size());
assertEquals(NodeIdentifier.create(list), result.get(0));
assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
@Test
void deserializePartInOtherModuleTest() {
final var result = assertNormalizedPath(
- "deserializer-test-included:augmented-list=100/deserializer-test:augmented-leaf").path.getPathArguments();
+ "deserializer-test-included:augmented-list=100/deserializer-test:augmented-leaf").getPathArguments();
assertEquals(3, result.size());
// list
@Test
void deserializeListInOtherModuleTest() {
final var result = assertNormalizedPath(
- "deserializer-test-included:augmented-list=100/deserializer-test:augmenting-list=0")
- .path.getPathArguments();
+ "deserializer-test-included:augmented-list=100/deserializer-test:augmenting-list=0").getPathArguments();
assertEquals(4, result.size());
// list
return errors.get(0);
}
- private static Result assertNormalizedPath(final String path) {
- final var result = NORMALIZER.normalizePath(assertApiPath(path));
- assertNotNull(result);
- return result;
+ private static Action assertNormalizedAction(final String path) {
+ return assertInstanceOf(Action.class, NORMALIZER.normalizePath(assertApiPath(path)));
+ }
+
+ private static YangInstanceIdentifier assertNormalizedPath(final String path) {
+ return assertInstanceOf(DataPath.class, NORMALIZER.normalizePath(assertApiPath(path))).instance();
}
private static ApiPath assertApiPath(final String path) {
}
private static void assertIdentityrefKeyValue(final String path) {
- final var pathArgs = assertNormalizedPath(path).path.getPathArguments();
+ final var pathArgs = assertNormalizedPath(path).getPathArguments();
assertEquals(4, pathArgs.size());
assertEquals("refs", pathArgs.get(0).getNodeType().getLocalName());