+++ /dev/null
-/*
- * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.netconf.client.mdsal;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.collect.Sets;
-import java.util.HashSet;
-import java.util.concurrent.Callable;
-import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchema;
-import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
-import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
-import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
-import org.opendaylight.netconf.client.mdsal.spi.NetconfDeviceRpc;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Schema building callable.
- */
-final class DeviceSourcesResolver implements Callable<DeviceSources> {
- private static final Logger LOG = LoggerFactory.getLogger(DeviceSourcesResolver.class);
-
- private final NetconfSessionPreferences remoteSessionCapabilities;
- private final NetconfDeviceSchemasResolver stateSchemasResolver;
- private final NetconfDeviceRpc deviceRpc;
- private final BaseNetconfSchema baseSchema;
- private final RemoteDeviceId id;
-
- DeviceSourcesResolver(final RemoteDeviceId id, final BaseNetconfSchema baseSchema, final NetconfDeviceRpc deviceRpc,
- final NetconfSessionPreferences remoteSessionCapabilities,
- final NetconfDeviceSchemasResolver stateSchemasResolver) {
- this.id = requireNonNull(id);
- this.baseSchema = requireNonNull(baseSchema);
- this.deviceRpc = requireNonNull(deviceRpc);
- this.remoteSessionCapabilities = requireNonNull(remoteSessionCapabilities);
- this.stateSchemasResolver = requireNonNull(stateSchemasResolver);
- }
-
- @Override
- public DeviceSources call() {
- final var availableSchemas = stateSchemasResolver.resolve(deviceRpc, remoteSessionCapabilities, id,
- baseSchema.modelContext());
- LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", id,
- availableSchemas.getAvailableYangSchemasQNames());
-
- final var requiredSources = new HashSet<>(remoteSessionCapabilities.moduleBasedCaps().keySet());
- final var providedSources = availableSchemas.getAvailableYangSchemasQNames();
- final var requiredSourcesNotProvided = Sets.difference(requiredSources, providedSources);
- if (!requiredSourcesNotProvided.isEmpty()) {
- LOG.warn("{}: Netconf device does not provide all yang models reported in hello message capabilities,"
- + " required but not provided: {}", id, requiredSourcesNotProvided);
- LOG.warn("{}: Attempting to build schema context from required sources", id);
- }
-
- // Here all the sources reported in netconf monitoring are merged with those reported in hello.
- // It is necessary to perform this since submodules are not mentioned in hello but still required.
- // This clashes with the option of a user to specify supported yang models manually in configuration
- // for netconf-connector and as a result one is not able to fully override yang models of a device.
- // It is only possible to add additional models.
- final var providedSourcesNotRequired = Sets.difference(providedSources, requiredSources);
- if (!providedSourcesNotRequired.isEmpty()) {
- LOG.warn("{}: Netconf device provides additional yang models not reported in "
- + "hello message capabilities: {}", id, providedSourcesNotRequired);
- LOG.warn("{}: Adding provided but not required sources as required to prevent failures", id);
- LOG.debug("{}: Netconf device reported in hello: {}", id, requiredSources);
- requiredSources.addAll(providedSourcesNotRequired);
- }
-
- final var sourceProvider = availableSchemas instanceof LibraryModulesSchemas libraryModule
- ? new LibrarySchemaSourceProvider(id, libraryModule.getAvailableModels())
- : new MonitoringSchemaSourceProvider(id, deviceRpc.domRpcService());
- return new DeviceSources(requiredSources, providedSources, sourceProvider);
- }
-}
\ No newline at end of file
import static java.util.Objects.requireNonNull;
import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_DATA_NODEID;
import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_NODEID;
+import static org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangModuleInfoImpl.qnameOf;
import com.google.common.base.Strings;
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 com.google.common.util.concurrent.SettableFuture;
import com.google.gson.stream.JsonReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
-import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.stream.XMLStreamException;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangLibrary;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.Module;
import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.Revision;
import org.opendaylight.yangtools.yang.common.XMLNamespace;
* ietf-netconf-yang-library/modules-state/modules node.
*/
public final class LibraryModulesSchemas implements NetconfDeviceSchemas {
-
private static final Logger LOG = LoggerFactory.getLogger(LibraryModulesSchemas.class);
private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
private static final EffectiveModelContext LIBRARY_CONTEXT = BindingRuntimeHelpers.createEffectiveModel(
// FIXME: this is legacy RFC7895, add support for RFC8525 containers, too
private static final NodeIdentifier MODULES_STATE_NID = NodeIdentifier.create(ModulesState.QNAME);
private static final NodeIdentifier MODULE_NID = NodeIdentifier.create(Module.QNAME);
- private static final NodeIdentifier NAME_NID = NodeIdentifier.create(QName.create(Module.QNAME, "name").intern());
- private static final NodeIdentifier REVISION_NID = NodeIdentifier.create(
- QName.create(Module.QNAME, "revision").intern());
- private static final NodeIdentifier SCHEMA_NID = NodeIdentifier.create(
- QName.create(Module.QNAME, "schema").intern());
- private static final NodeIdentifier NAMESPACE_NID = NodeIdentifier.create(
- QName.create(Module.QNAME, "namespace").intern());
+ private static final NodeIdentifier NAME_NID = NodeIdentifier.create(qnameOf("name"));
+ private static final NodeIdentifier REVISION_NID = NodeIdentifier.create(qnameOf("revision"));
+ private static final NodeIdentifier SCHEMA_NID = NodeIdentifier.create(qnameOf("schema"));
+ private static final NodeIdentifier NAMESPACE_NID = NodeIdentifier.create(qnameOf("namespace"));
private static final JSONCodecFactory JSON_CODECS = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02
.getShared(LIBRARY_CONTEXT);
.withNodeIdentifier(NETCONF_GET_NODEID)
.withChild(NetconfMessageTransformUtil.toFilterStructure(MODULES_STATE_MODULE_LIST, LIBRARY_CONTEXT))
.build();
+ private static final @NonNull LibraryModulesSchemas EMPTY = new LibraryModulesSchemas(ImmutableMap.of());
private final ImmutableMap<QName, URL> availableModels;
this.availableModels = requireNonNull(availableModels);
}
+ // FIXME: this should work on NetconfRpcService
+ public static ListenableFuture<LibraryModulesSchemas> forDevice(final NetconfDeviceRpc deviceRpc,
+ final RemoteDeviceId deviceId) {
+ final var future = SettableFuture.<LibraryModulesSchemas>create();
+ Futures.addCallback(deviceRpc.invokeNetconf(Get.QNAME, GET_MODULES_STATE_MODULE_LIST_RPC),
+ new FutureCallback<DOMRpcResult>() {
+ @Override
+ public void onSuccess(final DOMRpcResult result) {
+ onGetModulesStateResult(future, deviceId, result);
+ }
+
+ @Override
+ public void onFailure(final Throwable cause) {
+ // debug, because we expect this error to be reported by caller
+ LOG.debug("{}: Unable to detect available schemas", deviceId, cause);
+ future.setException(cause);
+ }
+ }, MoreExecutors.directExecutor());
+ return future;
+ }
+
+ private static void onGetModulesStateResult(final SettableFuture<LibraryModulesSchemas> future,
+ final RemoteDeviceId deviceId, final DOMRpcResult result) {
+ // Two-pass error reporting: first check if there is a hard error, then log any remaining warnings
+ final var errors = result.errors();
+ if (errors.stream().anyMatch(error -> error.getSeverity() == ErrorSeverity.ERROR)) {
+ // FIXME: a good exception, which can report the contents of errors?
+ future.setException(new OperationFailedException("Failed to get modules-state", errors));
+ return;
+ }
+ for (var error : errors) {
+ LOG.info("{}: schema retrieval warning: {}", deviceId, error);
+ }
+
+ final var value = result.value();
+ if (value == null) {
+ LOG.warn("{}: missing RPC output", deviceId);
+ future.set(EMPTY);
+ return;
+ }
+ final var data = value.childByArg(NETCONF_DATA_NODEID);
+ if (data == null) {
+ LOG.warn("{}: missing RPC data", deviceId);
+ future.set(EMPTY);
+ return;
+ }
+ // FIXME: right: this was never implemented correctly, as the origical code always produces CCE because it
+ // interpreted 'data' anyxml as a DataContainerNode!
+ future.setException(new UnsupportedOperationException("Missing normalization logic for " + data.prettyTree()));
+ }
+
public Map<SourceIdentifier, URL> getAvailableModels() {
- final Map<SourceIdentifier, URL> result = new HashMap<>();
- for (final Entry<QName, URL> entry : availableModels.entrySet()) {
- final SourceIdentifier sId = new SourceIdentifier(entry.getKey().getLocalName(),
+ final var result = new HashMap<SourceIdentifier, URL>();
+ for (var entry : availableModels.entrySet()) {
+ final var sId = new SourceIdentifier(entry.getKey().getLocalName(),
entry.getKey().getRevision().map(Revision::toString).orElse(null));
result.put(sId, entry.getValue());
}
}
}
-
- public static LibraryModulesSchemas create(final NetconfDeviceRpc deviceRpc, final RemoteDeviceId deviceId) {
- final DOMRpcResult moduleListNodeResult;
- try {
- moduleListNodeResult = deviceRpc.domRpcService().invokeRpc(Get.QNAME, GET_MODULES_STATE_MODULE_LIST_RPC)
- .get();
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IllegalStateException(deviceId + ": Interrupted while waiting for response to "
- + MODULES_STATE_MODULE_LIST, e);
- } catch (final ExecutionException e) {
- LOG.warn("{}: Unable to detect available schemas, get to {} failed", deviceId,
- MODULES_STATE_MODULE_LIST, e);
- return new LibraryModulesSchemas(ImmutableMap.of());
- }
-
- if (!moduleListNodeResult.errors().isEmpty()) {
- LOG.warn("{}: Unable to detect available schemas, get to {} failed, {}",
- deviceId, MODULES_STATE_MODULE_LIST, moduleListNodeResult.errors());
- return new LibraryModulesSchemas(ImmutableMap.of());
- }
-
- final Optional<DataContainerChild> modulesStateNode =
- findModulesStateNode(moduleListNodeResult.value());
- if (modulesStateNode.isPresent()) {
- final DataContainerChild node = modulesStateNode.orElseThrow();
- checkState(node instanceof ContainerNode, "Expecting container containing schemas, but was %s", node);
- return create((ContainerNode) node);
- }
-
- LOG.warn("{}: Unable to detect available schemas, get to {} was empty", deviceId, MODULES_STATE_NID);
- return new LibraryModulesSchemas(ImmutableMap.of());
- }
-
private static LibraryModulesSchemas create(final ContainerNode modulesStateNode) {
final Optional<DataContainerChild> moduleListNode = modulesStateNode.findChildByArg(MODULE_NID);
checkState(moduleListNode.isPresent(), "Unable to find list: %s in %s", MODULE_NID, modulesStateNode);
- final DataContainerChild node = moduleListNode.orElseThrow();
+ final var node = moduleListNode.orElseThrow();
checkState(node instanceof MapNode, "Unexpected structure for container: %s in : %s. Expecting a list",
MODULE_NID, modulesStateNode);
- final MapNode moduleList = (MapNode) node;
- final Collection<MapEntryNode> modules = moduleList.body();
- final ImmutableMap.Builder<QName, URL> schemasMapping = ImmutableMap.builderWithExpectedSize(modules.size());
- for (final MapEntryNode moduleNode : modules) {
- final Entry<QName, URL> entry = createFromEntry(moduleNode);
+ final var moduleList = (MapNode) node;
+ final var builder = ImmutableMap.<QName, URL>builderWithExpectedSize(moduleList.size());
+ for (var moduleNode : moduleList.body()) {
+ final var entry = createFromEntry(moduleNode);
if (entry != null) {
- schemasMapping.put(entry);
+ builder.put(entry);
}
}
- return new LibraryModulesSchemas(schemasMapping.build());
+ return new LibraryModulesSchemas(builder.build());
}
/**
return createFromURLConnection(connection);
}
- private static Optional<DataContainerChild> findModulesStateNode(final NormalizedNode result) {
- if (result == null) {
- return Optional.empty();
- }
- final Optional<DataContainerChild> dataNode = ((DataContainerNode) result).findChildByArg(NETCONF_DATA_NODEID);
- if (dataNode.isEmpty()) {
- return Optional.empty();
- }
-
- return ((DataContainerNode) dataNode.orElseThrow()).findChildByArg(MODULES_STATE_NID);
- }
-
private static LibraryModulesSchemas createFromURLConnection(final URLConnection connection) {
String contentType = connection.getContentType();
jsonParser.parse(reader);
- final NormalizationResult result = resultHolder.result();
+ final NormalizationResult<?> result = resultHolder.result();
return result == null ? Optional.empty() : Optional.of(result.data());
}
final var baseSchema = baseSchemas.baseSchemaForCapabilities(remoteSessionCapabilities);
final var initRpc = new NetconfDeviceRpc(baseSchema.modelContext(), listener,
new NetconfMessageTransformer(baseSchema.mountPointContext(), false, baseSchema));
- final var sourceResolverFuture = Futures.submit(new DeviceSourcesResolver(id, baseSchema, initRpc,
- remoteSessionCapabilities, stateSchemasResolver), processingExecutor);
+
+ // Acquire schemas
+ final var futureSchemas = stateSchemasResolver.resolve(initRpc, remoteSessionCapabilities, id,
+ baseSchema.modelContext());
+
+ // Convert to sources
+ final var sourceResolverFuture = Futures.transform(futureSchemas, availableSchemas -> {
+ final var providedSources = availableSchemas.getAvailableYangSchemasQNames();
+ LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", id, providedSources);
+
+ final var requiredSources = new HashSet<>(remoteSessionCapabilities.moduleBasedCaps().keySet());
+ final var requiredSourcesNotProvided = Sets.difference(requiredSources, providedSources);
+ if (!requiredSourcesNotProvided.isEmpty()) {
+ LOG.warn("{}: Netconf device does not provide all yang models reported in hello message capabilities,"
+ + " required but not provided: {}", id, requiredSourcesNotProvided);
+ LOG.warn("{}: Attempting to build schema context from required sources", id);
+ }
+
+ // Here all the sources reported in netconf monitoring are merged with those reported in hello.
+ // It is necessary to perform this since submodules are not mentioned in hello but still required.
+ // This clashes with the option of a user to specify supported yang models manually in configuration
+ // for netconf-connector and as a result one is not able to fully override yang models of a device.
+ // It is only possible to add additional models.
+ final var providedSourcesNotRequired = Sets.difference(providedSources, requiredSources);
+ if (!providedSourcesNotRequired.isEmpty()) {
+ LOG.warn("{}: Netconf device provides additional yang models not reported in "
+ + "hello message capabilities: {}", id, providedSourcesNotRequired);
+ LOG.warn("{}: Adding provided but not required sources as required to prevent failures", id);
+ LOG.debug("{}: Netconf device reported in hello: {}", id, requiredSources);
+ requiredSources.addAll(providedSourcesNotRequired);
+ }
+
+ final var sourceProvider = availableSchemas instanceof LibraryModulesSchemas libraryModule
+ ? new LibrarySchemaSourceProvider(id, libraryModule.getAvailableModels())
+ : new MonitoringSchemaSourceProvider(id, initRpc.domRpcService());
+ return new DeviceSources(requiredSources, providedSources, sourceProvider);
+ }, MoreExecutors.directExecutor());
if (shouldListenOnSchemaChange(remoteSessionCapabilities)) {
registerToBaseNetconfStream(initRpc, listener);
*/
package org.opendaylight.netconf.client.mdsal;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.base.Verify.verify;
import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_DATA_NODEID;
-import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.toId;
+import static org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.YangModuleInfoImpl.qnameOf;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
+import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableSet;
+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 com.google.common.util.concurrent.SettableFuture;
import java.io.IOException;
import java.net.URISyntaxException;
-import java.util.HashSet;
-import java.util.Optional;
+import java.time.format.DateTimeParseException;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.dom.DOMSource;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.dom.api.DOMRpcResult;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.netconf.api.NamespaceURN;
import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas;
import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
-import org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil;
import org.opendaylight.netconf.common.mdsal.NormalizedDataUtil;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.Get;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.GetInput;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Yang;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.Schema;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.schemas.Schema.Location;
+import org.opendaylight.yangtools.yang.common.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Revision;
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.schema.AnyxmlNode;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DOMSourceAnyxmlNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
-import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemLeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
-import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
public static final NetconfStateSchemas EMPTY = new NetconfStateSchemas(ImmutableSet.of());
private static final Logger LOG = LoggerFactory.getLogger(NetconfStateSchemas.class);
- private static final YangInstanceIdentifier STATE_SCHEMAS_IDENTIFIER = YangInstanceIdentifier.builder()
- .node(NetconfState.QNAME).node(Schemas.QNAME).build();
private static final String MONITORING_NAMESPACE = NetconfState.QNAME.getNamespace().toString();
+ private static final @NonNull NodeIdentifier SCHEMA_FORMAT_NODEID = NodeIdentifier.create(qnameOf("format"));
+ private static final @NonNull NodeIdentifier SCHEMA_LOCATION_NODEID = NodeIdentifier.create(qnameOf("location"));
+ private static final @NonNull NodeIdentifier SCHEMA_NAMESPACE_NODEID = NodeIdentifier.create(qnameOf("namespace"));
+ private static final @NonNull NodeIdentifier SCHEMA_IDENTIFIER_NODEID =
+ NodeIdentifier.create(qnameOf("identifier"));
+ private static final @NonNull NodeIdentifier SCHEMA_VERSION_NODEID = NodeIdentifier.create(qnameOf("version"));
+ private static final @NonNull String NETCONF_LOCATION = Location.Enumeration.NETCONF.getName();
private static final @NonNull ContainerNode GET_SCHEMAS_RPC;
static {
- final Document document = XmlUtil.newDocument();
-
- final Element filterElem = document.createElementNS(NamespaceURN.BASE, "filter");
+ final var document = XmlUtil.newDocument();
+ final var filterElem = document.createElementNS(NamespaceURN.BASE, "filter");
filterElem.setAttribute("type", "subtree");
- final Element stateElem = document.createElementNS(NetconfState.QNAME.getNamespace().toString(),
+ final var stateElem = document.createElementNS(NetconfState.QNAME.getNamespace().toString(),
NetconfState.QNAME.getLocalName());
stateElem.appendChild(document.createElementNS(Schemas.QNAME.getNamespace().toString(),
Schemas.QNAME.getLocalName()));
.build();
}
- private final Set<RemoteYangSchema> availableYangSchemas;
-
- public NetconfStateSchemas(final Set<RemoteYangSchema> availableYangSchemas) {
- this.availableYangSchemas = availableYangSchemas;
- }
+ private final ImmutableSet<QName> availableYangSchemasQNames;
- public Set<RemoteYangSchema> getAvailableYangSchemas() {
- return availableYangSchemas;
+ public NetconfStateSchemas(final Set<QName> availableYangSchemasQNames) {
+ this.availableYangSchemasQNames = ImmutableSet.copyOf(availableYangSchemasQNames);
}
@Override
public Set<QName> getAvailableYangSchemasQNames() {
- return availableYangSchemas.stream()
- .map(RemoteYangSchema::getQName)
- .collect(ImmutableSet.toImmutableSet());
+ return availableYangSchemasQNames;
}
/**
* Issue get request to remote device and parse response to find all schemas under netconf-state/schemas.
*/
- static NetconfStateSchemas create(final DOMRpcService deviceRpc,
+ static ListenableFuture<NetconfStateSchemas> forDevice(final DOMRpcService deviceRpc,
final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id,
- final EffectiveModelContext schemaContext) {
+ final EffectiveModelContext modelContext) {
if (!remoteSessionCapabilities.isMonitoringSupported()) {
// TODO - need to search for get-schema support, not just ietf-netconf-monitoring support
// issue might be a deviation to ietf-netconf-monitoring where get-schema is unsupported...
LOG.warn("{}: Netconf monitoring not supported on device, cannot detect provided schemas", id);
- return EMPTY;
+ return Futures.immediateFuture(EMPTY);
}
- final DOMRpcResult schemasNodeResult;
+ final var future = SettableFuture.<NetconfStateSchemas>create();
+ Futures.addCallback(deviceRpc.invokeRpc(Get.QNAME, GET_SCHEMAS_RPC),
+ new FutureCallback<DOMRpcResult>() {
+ @Override
+ public void onSuccess(final DOMRpcResult result) {
+ onGetSchemasResult(future, id, modelContext, result);
+ }
+
+ @Override
+ public void onFailure(final Throwable cause) {
+ // debug, because we expect this error to be reported by caller
+ LOG.debug("{}: Unable to detect available schemas", id, cause);
+ future.setException(cause);
+ }
+ }, MoreExecutors.directExecutor());
+ return future;
+ }
+
+ private static void onGetSchemasResult(final SettableFuture<NetconfStateSchemas> future, final RemoteDeviceId id,
+ final EffectiveModelContext modelContext, final DOMRpcResult result) {
+ // Two-pass error reporting: first check if there is a hard error, then log any remaining warnings
+ final var errors = result.errors();
+ if (errors.stream().anyMatch(error -> error.getSeverity() == ErrorSeverity.ERROR)) {
+ // FIXME: a good exception, which can report the contents of errors?
+ future.setException(new OperationFailedException("Failed to get netconf-state", errors));
+ return;
+ }
+ for (var error : errors) {
+ LOG.info("{}: schema retrieval warning: {}", id, error);
+ }
+
+ final var value = result.value();
+ if (value == null) {
+ LOG.warn("{}: missing RPC output", id);
+ future.set(EMPTY);
+ return;
+ }
+ final var data = value.childByArg(NETCONF_DATA_NODEID);
+ if (data == null) {
+ LOG.warn("{}: missing RPC data", id);
+ future.set(EMPTY);
+ return;
+ }
+ if (!(data instanceof AnyxmlNode<?> anyxmlData)) {
+ future.setException(new VerifyException("Unexpected data " + data.prettyTree()));
+ return;
+ }
+ final var dataBody = anyxmlData.body();
+ if (!(dataBody instanceof DOMSource domDataBody)) {
+ future.setException(new VerifyException("Unexpected body " + dataBody));
+ return;
+ }
+
+ // Server may include additional data which we do not understand. Make sure we trim the input before we try
+ // to interpret it.
+ // FIXME: we should not be going to NormalizedNode at all. We are interpreting a very limited set of data
+ // in the context of setting up the normalization schema. Everything we are dealing with are plain
+ // strings for which yang-common provides everything we need -- with the notable exception of identityref
+ // values. Those boil down into plain QNames -- so we can talk to XmlCodecs.identityRefCodec(). That
+ // operation needs to also handle IAE and ignore unknown values.
+ final var filteredBody = ietfMonitoringCopy(domDataBody);
+
+ // Now normalize the anyxml content to the selected model context
+ final NormalizedNode normalizedData;
try {
- schemasNodeResult = deviceRpc.invokeRpc(Get.QNAME, GET_SCHEMAS_RPC).get();
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IllegalStateException(id
- + ": Interrupted while waiting for response to " + STATE_SCHEMAS_IDENTIFIER, e);
- } catch (final ExecutionException e) {
- LOG.warn("{}: Unable to detect available schemas, get to {} failed", id, STATE_SCHEMAS_IDENTIFIER, e);
- return EMPTY;
+ normalizedData = NormalizedDataUtil.transformDOMSourceToNormalizedNode(modelContext, filteredBody)
+ .getResult().data();
+ } catch (XMLStreamException | URISyntaxException | IOException | SAXException e) {
+ LOG.debug("{}: failed to transform {}", id, filteredBody, e);
+ future.setException(e);
+ return;
+ }
+
+ // The result should be the root of datastore, hence a DataContainerNode
+ if (!(normalizedData instanceof DataContainerNode root)) {
+ future.setException(new VerifyException("Unexpected normalized data " + normalizedData.prettyTree()));
+ return;
}
- if (!schemasNodeResult.errors().isEmpty()) {
- LOG.warn("{}: Unable to detect available schemas, get to {} failed, {}",
- id, STATE_SCHEMAS_IDENTIFIER, schemasNodeResult.errors());
- return EMPTY;
+ // container netconf-state
+ final var netconfState = root.childByArg(new NodeIdentifier(NetconfState.QNAME));
+ if (netconfState == null) {
+ LOG.warn("{}: missing netconf-state", id);
+ future.set(EMPTY);
+ return;
+ }
+ if (!(netconfState instanceof ContainerNode netconfStateCont)) {
+ future.setException(new VerifyException("Unexpected netconf-state " + netconfState.prettyTree()));
+ return;
}
- final Optional<? extends NormalizedNode> optSchemasNode = findSchemasNode(schemasNodeResult.value(),
- schemaContext);
- if (optSchemasNode.isEmpty()) {
- LOG.warn("{}: Unable to detect available schemas, get to {} was empty", id, STATE_SCHEMAS_IDENTIFIER);
- return EMPTY;
+ // container schemas
+ final var schemas = netconfStateCont.childByArg(new NodeIdentifier(Schemas.QNAME));
+ if (schemas == null) {
+ LOG.warn("{}: missing schemas", id);
+ future.set(EMPTY);
+ return;
+ }
+ if (!(schemas instanceof ContainerNode schemasNode)) {
+ future.setException(new VerifyException("Unexpected schemas " + schemas.prettyTree()));
+ return;
}
- final NormalizedNode schemasNode = optSchemasNode.orElseThrow();
- checkState(schemasNode instanceof ContainerNode, "Expecting container containing schemas, but was %s",
- schemasNode);
- return create(id, (ContainerNode) schemasNode);
+ create(future, id, schemasNode);
}
/**
* Parse response of get(netconf-state/schemas) to find all schemas under netconf-state/schemas.
*/
@VisibleForTesting
- protected static NetconfStateSchemas create(final RemoteDeviceId id, final ContainerNode schemasNode) {
- final Set<RemoteYangSchema> availableYangSchemas = new HashSet<>();
-
- final DataContainerChild child = schemasNode.childByArg(toId(Schema.QNAME));
- checkState(child != null, "Unable to find list: %s in response: %s", Schema.QNAME.withoutRevision(),
- schemasNode);
- checkState(child instanceof MapNode,
- "Unexpected structure for container: %s in response: %s. Expecting a list",
- Schema.QNAME.withoutRevision(), schemasNode);
-
- for (final MapEntryNode schemaNode : ((MapNode) child).body()) {
- final Optional<RemoteYangSchema> fromCompositeNode =
- RemoteYangSchema.createFromNormalizedNode(id, schemaNode);
- fromCompositeNode.ifPresent(availableYangSchemas::add);
+ static void create(final SettableFuture<NetconfStateSchemas> future, final RemoteDeviceId id,
+ final ContainerNode schemasNode) {
+ final var child = schemasNode.childByArg(new NodeIdentifier(Schema.QNAME));
+ if (child == null) {
+ LOG.warn("{}: missing schema", id);
+ future.set(EMPTY);
+ return;
+ }
+ if (!(child instanceof SystemMapNode schemaMap)) {
+ future.setException(new VerifyException("Unexpected schemas " + child.prettyTree()));
+ return;
}
- return new NetconfStateSchemas(availableYangSchemas);
+ // FIXME: we are producing the wrong thing here and simply not handling all the use cases
+ // - instead of QName we want to say 'SourceIdentifier and XMLNamespace', because these are source files
+ // and there is some namespace guidance -- which we do not really need (because localName+revision is
+ // guaranteed to be unique and hence there cannot be a conflict on submodule names
+ // - we handle on "NETCONF" and completely ignore the URI case -- which is something useful for
+ // offloading model discovery
+ //
+ // At the end of the day, all this information is going into yang-parser-impl, i.e. it will need to go
+ // through SchemaSource and all the yang-repo-{api,spi} stuff. That implies policy and further control
+ // point which needs to be customizable as we want to plug in various providers and differing policies.
+ //
+ // A few examples:
+ // - all URIs need to be resolved, which needs pluggable resolvers (https:// is obvious, but xri:// needs
+ // to hand this off to a dedicated resolver
+ // - we do not want to use URI.toURL().openConnection(), but leave it up to policy -- for example one
+ // would want to use java.net.http.HttpClient, which means authentication and content negotiation.
+ // Content negotiation is needed to establish byte stream encoding, plus
+ // - all sources of schema are subject to caching, perhaps even in IRSource form
+ //
+ // At the end of the day, we should just produce an instance of Schema.class and let others deal with
+ // translating it to the real world -- for example turning a String into a XMLNamespace or a local name.
+ final var builder = ImmutableSet.<QName>builderWithExpectedSize(schemaMap.size());
+ for (var schemaNode : schemaMap.body()) {
+ final var qname = createFromNormalizedNode(id, schemaNode);
+ if (qname != null) {
+ builder.add(qname);
+ }
+ }
+ future.set(new NetconfStateSchemas(builder.build()));
}
- private static Optional<? extends NormalizedNode> findSchemasNode(final NormalizedNode result,
- final EffectiveModelContext schemaContext) {
- if (result == null) {
- return Optional.empty();
+ private static @Nullable QName createFromNormalizedNode(final RemoteDeviceId id, final MapEntryNode schemaEntry) {
+ // These three are mandatory due to 'key "identifier version format"'
+ final var format = schemaEntry.getChildByArg(SCHEMA_FORMAT_NODEID).body();
+ // FIXME: we should support Yin as well
+ if (!Yang.QNAME.equals(format)) {
+ LOG.debug("{}: Ignoring schema due to unsupported format: {}", id, format);
+ return null;
}
- // FIXME: unchecked cast
- final var rpcResult = ((ContainerNode) result).childByArg(NETCONF_DATA_NODEID);
- if (rpcResult == null) {
- return Optional.empty();
+ // Note: module name or submodule name
+ final var identifier = (String) schemaEntry.getChildByArg(SCHEMA_IDENTIFIER_NODEID).body();
+ // Note: revision
+ final var version = (String) schemaEntry.getChildByArg(SCHEMA_VERSION_NODEID).body();
+
+ // FIXME: we should be able to promote to 'getChildByArg()', IFF the normalizer is enforcing mandatory nodes
+ @SuppressWarnings("unchecked")
+ final var namespaceLeaf = (LeafNode<String>) schemaEntry.childByArg(SCHEMA_NAMESPACE_NODEID);
+ if (namespaceLeaf == null) {
+ LOG.warn("{}: Ignoring schema due to missing namespace", id);
+ return null;
}
- verify(rpcResult instanceof DOMSourceAnyxmlNode, "Unexpected result %s", rpcResult);
+ @SuppressWarnings("unchecked")
+ final var location = (SystemLeafSetNode<String>) schemaEntry.childByArg(SCHEMA_LOCATION_NODEID);
+ if (location == null) {
+ LOG.debug("{}: Ignoring schema due to missing location", id);
+ return null;
+ }
- // Server may include additional data which we do not understand. Make sure we trim the input before we try
- // to interpret it.
- // FIXME: this is something NetconfUtil.transformDOMSourceToNormalizedNode(), and more generally, NormalizedNode
- // codecs should handle. We really want to a NormalizedNode tree which can be directly queried for known
- // things while completely ignoring XML content (and hence its semantics) of other elements.
- final var filteredBody = ietfMonitoringCopy(((DOMSourceAnyxmlNode) rpcResult).body());
+ boolean foundNetconf = false;
+ for (var locEntry : location.body()) {
+ final var loc = locEntry.body();
+ if (NETCONF_LOCATION.equals(loc)) {
+ foundNetconf = true;
+ break;
+ }
- final NormalizedNode dataNode;
- try {
- dataNode = NormalizedDataUtil.transformDOMSourceToNormalizedNode(schemaContext, filteredBody).getResult()
- .data();
- } catch (XMLStreamException | URISyntaxException | IOException | SAXException e) {
- LOG.warn("Failed to transform {}", rpcResult, e);
- return Optional.empty();
+ // FIXME: the other string is an Uri, we should be exposing that as well
+ LOG.debug("{}: Ignoring schema due to unsupported location: {}", id, loc);
}
- // FIXME: unchecked cast
- final var nStateNode = ((DataContainerNode) dataNode).childByArg(toId(NetconfState.QNAME));
- if (nStateNode == null) {
- return Optional.empty();
+ if (!foundNetconf) {
+ LOG.debug("{}: Ignoring schema due to no NETCONF location", id);
+ return null;
}
- // FIXME: unchecked cast
- return ((DataContainerNode) nStateNode).findChildByArg(toId(Schemas.QNAME));
+ try {
+ final var namespace = XMLNamespace.of(namespaceLeaf.body());
+ final var revision = version.isEmpty() ? null : Revision.of(version);
+ return QName.create(namespace, revision, identifier);
+ } catch (DateTimeParseException | IllegalArgumentException e) {
+ LOG.warn("{}: Ignoring malformed schema {}", id, schemaEntry.prettyTree(), e);
+ return null;
+ }
}
@VisibleForTesting
}
}
}
-
- public static final class RemoteYangSchema {
- private final QName qname;
-
- RemoteYangSchema(final QName qname) {
- this.qname = qname;
- }
-
- public QName getQName() {
- return qname;
- }
-
- static Optional<RemoteYangSchema> createFromNormalizedNode(final RemoteDeviceId id,
- final MapEntryNode schemaNode) {
- final QName schemaNodeId = schemaNode.name().getNodeType();
- checkArgument(schemaNodeId.equals(Schema.QNAME), "Wrong QName %s", schemaNodeId);
-
- QName childNode = NetconfMessageTransformUtil.IETF_NETCONF_MONITORING_SCHEMA_FORMAT;
-
- final String formatAsString = getSingleChildNodeValue(schemaNode, childNode).orElseThrow();
-
- if (!formatAsString.equals(Yang.QNAME.toString())) {
- LOG.debug("{}: Ignoring schema due to unsupported format: {}", id, formatAsString);
- return Optional.empty();
- }
-
- childNode = NetconfMessageTransformUtil.IETF_NETCONF_MONITORING_SCHEMA_LOCATION;
- final Set<String> locationsAsString = getAllChildNodeValues(schemaNode, childNode);
- if (!locationsAsString.contains(Schema.Location.Enumeration.NETCONF.toString())) {
- LOG.debug("{}: Ignoring schema due to unsupported location: {}", id, locationsAsString);
- return Optional.empty();
- }
-
- childNode = NetconfMessageTransformUtil.IETF_NETCONF_MONITORING_SCHEMA_NAMESPACE;
- final Optional<String> namespaceValue = getSingleChildNodeValue(schemaNode, childNode);
- if (namespaceValue.isEmpty()) {
- LOG.warn("{}: Ignoring schema due to missing namespace", id);
- return Optional.empty();
- }
- final String namespaceAsString = namespaceValue.orElseThrow();
-
- childNode = NetconfMessageTransformUtil.IETF_NETCONF_MONITORING_SCHEMA_VERSION;
- // Revision does not have to be filled
- final Optional<String> revisionAsString = getSingleChildNodeValue(schemaNode, childNode);
-
- childNode = NetconfMessageTransformUtil.IETF_NETCONF_MONITORING_SCHEMA_IDENTIFIER;
- final String moduleNameAsString = getSingleChildNodeValue(schemaNode, childNode).orElseThrow();
-
- final QName moduleQName = revisionAsString.isPresent()
- ? QName.create(namespaceAsString, revisionAsString.orElseThrow(), moduleNameAsString)
- : QName.create(XMLNamespace.of(namespaceAsString), moduleNameAsString);
-
- return Optional.of(new RemoteYangSchema(moduleQName));
- }
-
- /**
- * Extracts all values of a leaf-list node as a set of strings.
- */
- private static Set<String> getAllChildNodeValues(final DataContainerNode schemaNode,
- final QName childNodeQName) {
- final Set<String> extractedValues = new HashSet<>();
- final DataContainerChild child = schemaNode.childByArg(toId(childNodeQName));
- checkArgument(child != null, "Child nodes %s not present", childNodeQName);
- checkArgument(child instanceof LeafSetNode, "Child nodes %s not present", childNodeQName);
- for (final LeafSetEntryNode<?> childNode : ((LeafSetNode<?>) child).body()) {
- extractedValues.add(getValueOfSimpleNode(childNode).orElseThrow());
- }
- return extractedValues;
- }
-
- private static Optional<String> getSingleChildNodeValue(final DataContainerNode schemaNode,
- final QName childNode) {
- final Optional<DataContainerChild> node = schemaNode.findChildByArg(toId(childNode));
- if (node.isPresent()) {
- return getValueOfSimpleNode(node.orElseThrow());
- }
- LOG.debug("Child node {} not present", childNode);
- return Optional.empty();
- }
-
- private static Optional<String> getValueOfSimpleNode(final NormalizedNode node) {
- final String valueStr = node.body().toString();
- return Strings.isNullOrEmpty(valueStr) ? Optional.empty() : Optional.of(valueStr.trim());
- }
-
- @Override
- public boolean equals(final Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
-
- final RemoteYangSchema that = (RemoteYangSchema) obj;
-
- return qname.equals(that.qname);
- }
-
- @Override
- public int hashCode() {
- return qname.hashCode();
- }
- }
}
*/
package org.opendaylight.netconf.client.mdsal;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas;
import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
.bindTo(QNameModule.create(RFC8525_YANG_LIBRARY_CAPABILITY.getNamespace(), Revision.of("2016-06-21"))).intern();
@Override
- public NetconfDeviceSchemas resolve(final NetconfDeviceRpc deviceRpc,
- final NetconfSessionPreferences remoteSessionCapabilities,
- final RemoteDeviceId id, final EffectiveModelContext schemaContext) {
+ public ListenableFuture<? extends NetconfDeviceSchemas> resolve(final NetconfDeviceRpc deviceRpc,
+ final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id,
+ final EffectiveModelContext schemaContext) {
// FIXME: I think we should prefer YANG library here
if (remoteSessionCapabilities.isMonitoringSupported()) {
- return NetconfStateSchemas.create(deviceRpc.domRpcService(), remoteSessionCapabilities, id, schemaContext);
+ return NetconfStateSchemas.forDevice(deviceRpc.domRpcService(), remoteSessionCapabilities, id,
+ schemaContext);
}
if (remoteSessionCapabilities.containsModuleCapability(RFC8525_YANG_LIBRARY_CAPABILITY)
|| remoteSessionCapabilities.containsModuleCapability(RFC7895_YANG_LIBRARY_CAPABILITY)) {
- return LibraryModulesSchemas.create(deviceRpc, id);
+ return LibraryModulesSchemas.forDevice(deviceRpc, id);
}
- return NetconfStateSchemas.EMPTY;
+ return Futures.immediateFuture(NetconfStateSchemas.EMPTY);
}
}
*/
package org.opendaylight.netconf.client.mdsal.api;
+import com.google.common.util.concurrent.ListenableFuture;
import org.opendaylight.netconf.client.mdsal.spi.NetconfDeviceRpc;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
*/
public interface NetconfDeviceSchemasResolver {
// FIXME: document this method
- NetconfDeviceSchemas resolve(NetconfDeviceRpc deviceRpc, NetconfSessionPreferences remoteSessionCapabilities,
- RemoteDeviceId id, EffectiveModelContext schemaContext);
+ ListenableFuture<? extends NetconfDeviceSchemas> resolve(NetconfDeviceRpc deviceRpc,
+ NetconfSessionPreferences remoteSessionCapabilities, RemoteDeviceId id, EffectiveModelContext schemaContext);
}
public static final @NonNull QName IETF_NETCONF_MONITORING =
QName.create(NetconfState.QNAME, "ietf-netconf-monitoring").intern();
- public static final @NonNull QName IETF_NETCONF_MONITORING_SCHEMA_FORMAT =
- QName.create(IETF_NETCONF_MONITORING, "format").intern();
- public static final @NonNull QName IETF_NETCONF_MONITORING_SCHEMA_LOCATION =
- QName.create(IETF_NETCONF_MONITORING, "location").intern();
- public static final @NonNull QName IETF_NETCONF_MONITORING_SCHEMA_IDENTIFIER =
- QName.create(IETF_NETCONF_MONITORING, "identifier").intern();
- public static final @NonNull QName IETF_NETCONF_MONITORING_SCHEMA_VERSION =
- QName.create(IETF_NETCONF_MONITORING, "version").intern();
- public static final @NonNull QName IETF_NETCONF_MONITORING_SCHEMA_NAMESPACE =
- QName.create(IETF_NETCONF_MONITORING, "namespace").intern();
public static final @NonNull NodeIdentifier NETCONF_DATA_NODEID = NodeIdentifier.create(Data.QNAME);
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin;
import org.opendaylight.yangtools.concepts.Registration;
import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
TEST_NAMESPACE + "?module=" + TEST_MODULE + "2" + "&revision=" + TEST_REVISION;
private static final NetconfDeviceSchemasResolver STATE_SCHEMAS_RESOLVER =
- (deviceRpc, remoteSessionCapabilities, id, schemaContext) -> NetconfStateSchemas.EMPTY;
+ (deviceRpc, remoteSessionCapabilities, id, schemaContext) -> Futures.immediateFuture(NetconfStateSchemas.EMPTY);
private static NetconfMessage NOTIFICATION;
}
}).when(schemaFactory).createEffectiveModelContext(anyCollection());
- final NetconfDeviceSchemasResolver stateSchemasResolver = (deviceRpc, remoteSessionCapabilities, id,
- schemaContext) -> {
- final Module first = SCHEMA_CONTEXT.getModules().iterator().next();
- final QName qName = QName.create(first.getQNameModule(), first.getName());
- final NetconfStateSchemas.RemoteYangSchema source1 = new NetconfStateSchemas.RemoteYangSchema(qName);
- final NetconfStateSchemas.RemoteYangSchema source2 =
- new NetconfStateSchemas.RemoteYangSchema(QName.create(first.getQNameModule(), "test-module2"));
- return new NetconfStateSchemas(Sets.newHashSet(source1, source2));
- };
+ final NetconfDeviceSchemasResolver stateSchemasResolver =
+ (deviceRpc, remoteSessionCapabilities, id, schemaContext) -> {
+ final var first = SCHEMA_CONTEXT.getModules().iterator().next();
+ final var qName = QName.create(first.getQNameModule(), first.getName());
+ return Futures.immediateFuture(new NetconfStateSchemas(
+ Set.of(qName, QName.create(first.getQNameModule(), "test-module2"))));
+ };
doReturn(mock(Registration.class)).when(schemaRegistry).registerSchemaSource(any(), any());
final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice
}
}).when(schemaFactory).createEffectiveModelContext(anyCollection());
- final NetconfDeviceSchemasResolver stateSchemasResolver = (deviceRpc, remoteSessionCapabilities, id,
- schemaContext) -> {
- final Module first = SCHEMA_CONTEXT.getModules().iterator().next();
- final QName qName = QName.create(first.getQNameModule(), first.getName());
- final NetconfStateSchemas.RemoteYangSchema source1 = new NetconfStateSchemas.RemoteYangSchema(qName);
- final NetconfStateSchemas.RemoteYangSchema source2 =
- new NetconfStateSchemas.RemoteYangSchema(QName.create(first.getQNameModule(), "test-module2"));
- return new NetconfStateSchemas(Sets.newHashSet(source1, source2));
- };
+ final NetconfDeviceSchemasResolver stateSchemasResolver =
+ (deviceRpc, remoteSessionCapabilities, id, schemaContext) -> {
+ final var first = SCHEMA_CONTEXT.getModules().iterator().next();
+ final var qName = QName.create(first.getQNameModule(), first.getName());
+ return Futures.immediateFuture(new NetconfStateSchemas(
+ Set.of(qName, QName.create(first.getQNameModule(), "test-module2"))));
+ };
doReturn(mock(Registration.class)).when(schemaRegistry).registerSchemaSource(any(), any());
final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice
package org.opendaylight.netconf.client.mdsal;
import static org.hamcrest.CoreMatchers.hasItem;
-import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
import java.net.InetSocketAddress;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
-import org.opendaylight.mdsal.dom.api.DOMRpcResult;
import org.opendaylight.mdsal.dom.api.DOMRpcService;
import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
import org.opendaylight.yangtools.util.xml.UntrustedXML;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class NetconfStateSchemasTest extends AbstractBaseSchemasTest {
- private static final Logger LOG = LoggerFactory.getLogger(NetconfStateSchemasTest.class);
private static final NetconfSessionPreferences CAPS = NetconfSessionPreferences.fromStrings(Set.of(
"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04"));
+ private static final RemoteDeviceId DEVICE_ID = new RemoteDeviceId("device", new InetSocketAddress(99));
- private final RemoteDeviceId deviceId = new RemoteDeviceId("device", new InetSocketAddress(99));
- private final int numberOfSchemas = 73;
- private final int numberOfLegalSchemas = numberOfSchemas - 3;
-
- private ContainerNode compositeNodeSchemas;
+ private static EffectiveModelContext MODEL_CONTEXT = BASE_SCHEMAS.baseSchemaForCapabilities(CAPS).modelContext();
+ private static ContainerNode SCHEMAS_PAYLOAD;
@Mock
private DOMRpcService rpc;
- private EffectiveModelContext modelContext;
-
- @Before
- public void setUp() throws Exception {
- modelContext = BASE_SCHEMAS.baseSchemaForCapabilities(CAPS).modelContext();
-
+ @BeforeClass
+ public static void setUp() throws Exception {
final var resultHolder = new NormalizationResultHolder();
final var writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
final var xmlParser = XmlParserStream.create(writer,
- SchemaInferenceStack.ofDataTreePath(modelContext, NetconfState.QNAME, Schemas.QNAME).toInference(), false);
+ SchemaInferenceStack.ofDataTreePath(MODEL_CONTEXT, NetconfState.QNAME, Schemas.QNAME).toInference(), false);
- xmlParser.parse(UntrustedXML.createXMLStreamReader(getClass().getResourceAsStream(
+ xmlParser.parse(UntrustedXML.createXMLStreamReader(NetconfStateSchemasTest.class.getResourceAsStream(
"/netconf-state.schemas.payload.xml")));
- compositeNodeSchemas = (ContainerNode) resultHolder.getResult().data();
+ SCHEMAS_PAYLOAD = (ContainerNode) resultHolder.getResult().data();
}
@Test
public void testCreate() throws Exception {
- final var schemas = NetconfStateSchemas.create(deviceId, compositeNodeSchemas);
+ final var future = SettableFuture.<NetconfStateSchemas>create();
+ NetconfStateSchemas.create(future, DEVICE_ID, SCHEMAS_PAYLOAD);
+ final var schemas = Futures.getDone(future);
final var availableYangSchemasQNames = schemas.getAvailableYangSchemasQNames();
- assertEquals(numberOfLegalSchemas, availableYangSchemasQNames.size());
+ assertEquals(69, availableYangSchemasQNames.size());
assertThat(availableYangSchemasQNames,
hasItem(QName.create("urn:TBD:params:xml:ns:yang:network-topology", "2013-07-12", "network-topology")));
}
- @Ignore
@Test
+ @Ignore("We cannot handle a container as data -- only anyxml")
public void testCreate2() throws Exception {
- final ContainerNode netconfState = ImmutableNodes.newContainerBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NetconfState.QNAME))
- .withChild(compositeNodeSchemas)
- .build();
- final ContainerNode data = ImmutableNodes.newContainerBuilder()
- .withNodeIdentifier(NetconfMessageTransformUtil.NETCONF_DATA_NODEID)
- .withChild(netconfState)
- .build();
final ContainerNode rpcReply = ImmutableNodes.newContainerBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier
- .NodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME))
- .withChild(data)
+ .withNodeIdentifier(new NodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME))
+ .withChild(ImmutableNodes.newContainerBuilder()
+ .withNodeIdentifier(NetconfMessageTransformUtil.NETCONF_DATA_NODEID)
+ .withChild(ImmutableNodes.newContainerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NetconfState.QNAME))
+ .withChild(SCHEMAS_PAYLOAD)
+ .build())
+ .build())
.build();
doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcReply))).when(rpc).invokeRpc(eq(Get.QNAME), any());
- final NetconfStateSchemas stateSchemas = NetconfStateSchemas.create(rpc, CAPS, deviceId, modelContext);
- final Set<QName> availableYangSchemasQNames = stateSchemas.getAvailableYangSchemasQNames();
- assertEquals(numberOfLegalSchemas, availableYangSchemasQNames.size());
+ final var stateSchemas = assertSchemas(CAPS);
+ final var availableYangSchemasQNames = stateSchemas.getAvailableYangSchemasQNames();
+ assertEquals(69, availableYangSchemasQNames.size());
assertThat(availableYangSchemasQNames,
hasItem(QName.create("urn:TBD:params:xml:ns:yang:network-topology", "2013-07-12", "network-topology")));
@Test
public void testCreateMonitoringNotSupported() throws Exception {
- final var caps = NetconfSessionPreferences.fromStrings(Set.of());
- final var stateSchemas = NetconfStateSchemas.create(rpc, caps, deviceId, modelContext);
+ final var stateSchemas = assertSchemas(NetconfSessionPreferences.fromStrings(Set.of()));
assertEquals(Set.of(), stateSchemas.getAvailableYangSchemasQNames());
}
@Test
public void testCreateFail() throws Exception {
- when(rpc.invokeRpc(eq(Get.QNAME), any())).thenReturn(
- Futures.immediateFailedFuture(new DOMRpcImplementationNotAvailableException("not available")));
- final var stateSchemas = NetconfStateSchemas.create(rpc, CAPS, deviceId, modelContext);
- assertEquals(Set.of(), stateSchemas.getAvailableYangSchemasQNames());
+ final var domEx = new DOMRpcImplementationNotAvailableException("not available");
+ doReturn(Futures.immediateFailedFuture(domEx)).when(rpc).invokeRpc(eq(Get.QNAME), any());
+ assertSame(domEx, assertSchemasFailure());
}
@Test
public void testCreateRpcError() throws Exception {
- final RpcError rpcError = RpcResultBuilder.newError(ErrorType.RPC, new ErrorTag("fail"), "fail");
- doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcError))).when(rpc)
- .invokeRpc(eq(Get.QNAME), any());
- final var stateSchemas = NetconfStateSchemas.create(rpc, CAPS, deviceId, modelContext);
- assertEquals(Set.of(), stateSchemas.getAvailableYangSchemasQNames());
+ final var rpcError = RpcResultBuilder.newError(ErrorType.RPC, new ErrorTag("fail"), "fail");
+ doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcError))).when(rpc).invokeRpc(eq(Get.QNAME), any());
+
+ final var ex = assertInstanceOf(OperationFailedException.class, assertSchemasFailure());
+ assertEquals(List.of(rpcError), ex.getErrorList());
}
- @Test
- public void testCreateInterrupted() {
- //NetconfStateSchemas.create calls Thread.currentThread().interrupt(), so it must run in its own thread
- final var testFuture = Executors.newSingleThreadExecutor().submit(() -> {
- final ListenableFuture<DOMRpcResult> interruptedFuture = mock(ListenableFuture.class);
- try {
- when(interruptedFuture.get()).thenThrow(new InterruptedException("interrupted"));
- doReturn(interruptedFuture).when(rpc).invokeRpc(eq(Get.QNAME), any());
- NetconfStateSchemas.create(rpc, CAPS, deviceId, modelContext);
- } catch (final InterruptedException | ExecutionException e) {
- LOG.info("Operation failed.", e);
- }
- });
-
- assertThat(assertThrows(ExecutionException.class, () -> testFuture.get(3, TimeUnit.SECONDS)).getCause(),
- instanceOf(RuntimeException.class));
+ private NetconfStateSchemas assertSchemas(final NetconfSessionPreferences prefs) {
+ try {
+ return Futures.getDone(NetconfStateSchemas.forDevice(rpc, prefs, DEVICE_ID, MODEL_CONTEXT));
+ } catch (ExecutionException e) {
+ throw new AssertionError(e);
+ }
}
- @Test
- public void testRemoteYangSchemaEquals() throws Exception {
- final NetconfStateSchemas.RemoteYangSchema schema1 =
- new NetconfStateSchemas.RemoteYangSchema(NetconfState.QNAME);
- final NetconfStateSchemas.RemoteYangSchema schema2 =
- new NetconfStateSchemas.RemoteYangSchema(NetconfState.QNAME);
- final NetconfStateSchemas.RemoteYangSchema schema3 =
- new NetconfStateSchemas.RemoteYangSchema(Schemas.QNAME);
- assertEquals(schema1, schema2);
- assertEquals(schema2, schema1);
- assertNotEquals(schema1, schema3);
- assertNotEquals(schema2, schema3);
+ private Throwable assertSchemasFailure() {
+ final var future = NetconfStateSchemas.forDevice(rpc, CAPS, DEVICE_ID, MODEL_CONTEXT);
+ return assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause();
}
}