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.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_QNAME;
+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.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
-import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
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.netconf.client.mdsal.api.RemoteDeviceId;
import org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil;
import org.opendaylight.netconf.client.mdsal.spi.NetconfDeviceRpc;
+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.yang.ietf.yang.library.rev190104.ModulesState;
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;
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.stream.NormalizationResult;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
-import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
-import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
+import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
import org.slf4j.Logger;
* 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);
private static final YangInstanceIdentifier MODULES_STATE_MODULE_LIST =
- YangInstanceIdentifier.create(MODULES_STATE_NID, MODULE_NID);
+ YangInstanceIdentifier.of(MODULES_STATE_NID, MODULE_NID);
- private static final @NonNull ContainerNode GET_MODULES_STATE_MODULE_LIST_RPC = Builders.containerBuilder()
+ private static final @NonNull ContainerNode GET_MODULES_STATE_MODULE_LIST_RPC = ImmutableNodes.newContainerBuilder()
.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.invokeRpc(NETCONF_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();
checkState(modulesStateNode instanceof ContainerNode, "Expecting container containing module list, but was %s",
modulesStateNode);
final ContainerNode modulesState = (ContainerNode) modulesStateNode;
- final NodeIdentifier nodeName = modulesState.getIdentifier();
+ final NodeIdentifier nodeName = modulesState.name();
checkState(MODULES_STATE_NID.equals(nodeName), "Wrong container identifier %s", nodeName);
return create((ContainerNode) modulesStateNode);
}
private static Optional<NormalizedNode> readJson(final InputStream in) {
- final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizationResultHolder resultHolder = new NormalizationResultHolder();
final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
final JsonParserStream jsonParser = JsonParserStream.create(writer, JSON_CODECS);
jsonParser.parse(reader);
- return resultHolder.isFinished() ? Optional.of(resultHolder.getResult()) : Optional.empty();
+ final NormalizationResult<?> result = resultHolder.result();
+ return result == null ? Optional.empty() : Optional.of(result.data());
}
private static Optional<NormalizedNode> readXml(final InputStream in) {
}
}
- final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
+ final NormalizationResultHolder resultHolder = new NormalizationResultHolder();
final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
final XmlParserStream xmlParser = XmlParserStream.create(writer, MODULES_STATE_INFERENCE);
xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
- return Optional.of(resultHolder.getResult());
- } catch (XMLStreamException | URISyntaxException | IOException | SAXException e) {
+ return Optional.of(resultHolder.getResult().data());
+ } catch (XMLStreamException | IOException | SAXException e) {
LOG.warn("Unable to parse yang library xml content", e);
}
}
private static @Nullable Entry<QName, URL> createFromEntry(final MapEntryNode moduleNode) {
- final QName moduleNodeId = moduleNode.getIdentifier().getNodeType();
+ final QName moduleNodeId = moduleNode.name().getNodeType();
checkArgument(moduleNodeId.equals(Module.QNAME), "Wrong QName %s", moduleNodeId);
final String moduleName = getSingleChildNodeValue(moduleNode, NAME_NID).orElseThrow();