X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=plugins%2Fnetconf-client-mdsal%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fclient%2Fmdsal%2Fimpl%2FNetconfStateSchemasResolverImpl.java;fp=plugins%2Fnetconf-client-mdsal%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fclient%2Fmdsal%2FNetconfStateSchemas.java;h=fdad493d2efc188dfba139f4ca93204ebc7c9f5a;hb=e3355e17dd5ebcd3b92f99ff528e61d3d9ef7fe9;hp=41b2beefb15e90cf20e8e27744ec19958447078e;hpb=61fd387f1a6cefdf913996bfd4b27e5ad636723d;p=netconf.git diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemas.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/NetconfStateSchemasResolverImpl.java similarity index 61% rename from plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemas.java rename to plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/NetconfStateSchemasResolverImpl.java index 41b2beefb1..fdad493d2e 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemas.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/NetconfStateSchemasResolverImpl.java @@ -1,27 +1,29 @@ /* - * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved. + * 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.netconf.client.mdsal; +package org.opendaylight.netconf.client.mdsal.impl; import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_DATA_NODEID; 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.VerifyException; import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.FutureCallback; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.AsyncFunction; 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.time.format.DateTimeParseException; +import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import javax.xml.stream.XMLStreamException; import javax.xml.transform.dom.DOMSource; import org.eclipse.jdt.annotation.NonNull; @@ -30,8 +32,10 @@ import org.opendaylight.mdsal.dom.api.DOMRpcResult; import org.opendaylight.netconf.api.NamespaceURN; import org.opendaylight.netconf.api.xml.XmlUtil; import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas; +import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver; import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService; import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences; +import org.opendaylight.netconf.client.mdsal.api.ProvidedSources; import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId; import org.opendaylight.netconf.common.mdsal.NormalizedDataUtil; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.Get; @@ -43,7 +47,6 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.mon 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; @@ -58,6 +61,8 @@ 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.opendaylight.yangtools.yang.model.api.source.YangTextSource; +import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -68,12 +73,10 @@ import org.w3c.dom.traversal.TreeWalker; import org.xml.sax.SAXException; /** - * Holds QNames for all YANG modules reported by ietf-netconf-monitoring/state/schemas. + * Default implementation resolving schemas QNames from netconf-state or from modules-state. */ -public final class NetconfStateSchemas implements NetconfDeviceSchemas { - public static final NetconfStateSchemas EMPTY = new NetconfStateSchemas(ImmutableSet.of()); - - private static final Logger LOG = LoggerFactory.getLogger(NetconfStateSchemas.class); +public final class NetconfStateSchemasResolverImpl implements NetconfDeviceSchemasResolver { + private static final Logger LOG = LoggerFactory.getLogger(NetconfStateSchemasResolverImpl.class); 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")); @@ -104,81 +107,86 @@ public final class NetconfStateSchemas implements NetconfDeviceSchemas { .build(); } - private final ImmutableSet availableYangSchemasQNames; - - public NetconfStateSchemas(final Set availableYangSchemasQNames) { - this.availableYangSchemasQNames = ImmutableSet.copyOf(availableYangSchemasQNames); - } - @Override - public Set getAvailableYangSchemasQNames() { - return availableYangSchemasQNames; - } + public ListenableFuture resolve(final RemoteDeviceId deviceId, + final NetconfSessionPreferences sessionPreferences, final NetconfRpcService deviceRpc, + final EffectiveModelContext baseModelContext) { + // Find all schema sources provided via ietf-netconf-monitoring, if supported + final var monitoringFuture = sessionPreferences.isMonitoringSupported() + ? resolveMonitoringSources(deviceId, deviceRpc, baseModelContext) + : Futures.immediateFuture(List.>of()); + + final AsyncFunction>, NetconfDeviceSchemas> function; + LOG.debug("{}: resolving YANG 1.0 conformance", deviceId); + function = sources -> resolveYang10(deviceId, sessionPreferences, deviceRpc, baseModelContext, sources); + + // FIXME: check for + // urn:ietf:params:netconf:capability:yang-library:1.0?revision=&module-set-id= + // + // and then dispatch to resolveYang11(), which should resolve schemas based on RFC7950's conformance + // announcement via I message, as defined in + // https://www.rfc-editor.org/rfc/rfc6020#section-5.6.4 + // + // if (sessionPreferences.containsNonModuleCapability(CapabilityURN.YANG_LIBRARY)) { + // LOG.debug("{}: resolving YANG 1.1 conformance", deviceId); + // function = sources -> resolveYang11(deviceId, sessionPreferences, deviceRpc, baseModelContext, + // sources); + // } + + // FIXME: check for + // urn:ietf:params:netconf:capability:yang-library:1.1?revision=&content-id= + // where date is at least 2019-01-04 + // + // and then dispatch to resolveNmda(): + // + // if (sessionPreferences.containsNonModuleCapability(CapabilityURN.YANG_LIBRARY)) { + // LOG.debug("{}: resolving YANG 1.1 NMDA conformance", deviceId); + // function = sources -> resolveNmda(deviceId, sessionPreferences, deviceRpc, baseModelContext, + // sources); + // } - /** - * Issue get request to remote device and parse response to find all schemas under netconf-state/schemas. - */ - static ListenableFuture forDevice(final NetconfRpcService deviceRpc, - final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id, - 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 Futures.immediateFuture(EMPTY); - } + return Futures.transformAsync(monitoringFuture, function, MoreExecutors.directExecutor()); + } - final var future = SettableFuture.create(); - Futures.addCallback(deviceRpc.invokeNetconf(Get.QNAME, GET_SCHEMAS_RPC), - new FutureCallback() { - @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 ListenableFuture>> resolveMonitoringSources( + final RemoteDeviceId deviceId, final NetconfRpcService deviceRpc, + final EffectiveModelContext baseModelContext) { + return Futures.transform(deviceRpc.invokeNetconf(Get.QNAME, GET_SCHEMAS_RPC), + result -> resolveMonitoringSources(deviceId, deviceRpc, result, baseModelContext), + MoreExecutors.directExecutor()); } - private static void onGetSchemasResult(final SettableFuture future, final RemoteDeviceId id, - final EffectiveModelContext modelContext, final DOMRpcResult result) { + private static List> resolveMonitoringSources(final RemoteDeviceId deviceId, + final NetconfRpcService deviceRpc, final DOMRpcResult rpcResult, + final EffectiveModelContext baseModelContext) { // Two-pass error reporting: first check if there is a hard error, then log any remaining warnings - final var errors = result.errors(); + final var errors = rpcResult.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; + LOG.warn("{}: failed to get netconf-state", errors); + return List.of(); } for (var error : errors) { - LOG.info("{}: schema retrieval warning: {}", id, error); + LOG.info("{}: schema retrieval warning: {}", deviceId, error); } - final var value = result.value(); - if (value == null) { - LOG.warn("{}: missing RPC output", id); - future.set(EMPTY); - return; + final var rpcOutput = rpcResult.value(); + if (rpcOutput == null) { + LOG.warn("{}: missing RPC output", deviceId); + return List.of(); } - final var data = value.childByArg(NETCONF_DATA_NODEID); + final var data = rpcOutput.childByArg(NETCONF_DATA_NODEID); if (data == null) { - LOG.warn("{}: missing RPC data", id); - future.set(EMPTY); - return; + LOG.warn("{}: missing RPC data", deviceId); + return List.of(); } if (!(data instanceof AnyxmlNode anyxmlData)) { - future.setException(new VerifyException("Unexpected data " + data.prettyTree())); - return; + LOG.warn("{}: unexpected data {}", deviceId, data.prettyTree()); + return List.of(); } final var dataBody = anyxmlData.body(); if (!(dataBody instanceof DOMSource domDataBody)) { - future.setException(new VerifyException("Unexpected body " + dataBody)); - return; + LOG.warn("{}: unexpected body {}", deviceId, dataBody); + return List.of(); } // Server may include additional data which we do not understand. Make sure we trim the input before we try @@ -193,62 +201,58 @@ public final class NetconfStateSchemas implements NetconfDeviceSchemas { // Now normalize the anyxml content to the selected model context final NormalizedNode normalizedData; try { - normalizedData = NormalizedDataUtil.transformDOMSourceToNormalizedNode(modelContext, filteredBody) + normalizedData = NormalizedDataUtil.transformDOMSourceToNormalizedNode(baseModelContext, filteredBody) .getResult().data(); } catch (XMLStreamException | URISyntaxException | IOException | SAXException e) { - LOG.debug("{}: failed to transform {}", id, filteredBody, e); - future.setException(e); - return; + LOG.warn("{}: failed to transform {}", deviceId, filteredBody, e); + return List.of(); } // 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; + LOG.warn("{}: unexpected normalized data {}", deviceId, normalizedData.prettyTree()); + return List.of(); } // 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; + LOG.warn("{}: missing netconf-state", deviceId); + return List.of(); } if (!(netconfState instanceof ContainerNode netconfStateCont)) { - future.setException(new VerifyException("Unexpected netconf-state " + netconfState.prettyTree())); - return; + LOG.warn("{}: unexpected netconf-state {}", deviceId, netconfState.prettyTree()); + return List.of(); } // container schemas final var schemas = netconfStateCont.childByArg(new NodeIdentifier(Schemas.QNAME)); if (schemas == null) { - LOG.warn("{}: missing schemas", id); - future.set(EMPTY); - return; + LOG.warn("{}: missing schemas", deviceId); + return List.of(); } if (!(schemas instanceof ContainerNode schemasNode)) { - future.setException(new VerifyException("Unexpected schemas " + schemas.prettyTree())); - return; + LOG.warn("{}: unexpected schemas {}", deviceId, schemas.prettyTree()); + return List.of(); } - create(future, id, schemasNode); + return resolveMonitoringSources(deviceId, deviceRpc, schemasNode); } /** * Parse response of get(netconf-state/schemas) to find all schemas under netconf-state/schemas. */ @VisibleForTesting - static void create(final SettableFuture future, final RemoteDeviceId id, - final ContainerNode schemasNode) { + static List> resolveMonitoringSources(final RemoteDeviceId deviceId, + final NetconfRpcService deviceRpc, final ContainerNode schemasNode) { final var child = schemasNode.childByArg(new NodeIdentifier(Schema.QNAME)); if (child == null) { - LOG.warn("{}: missing schema", id); - future.set(EMPTY); - return; + LOG.warn("{}: missing schema", deviceId); + return List.of(); } if (!(child instanceof SystemMapNode schemaMap)) { - future.setException(new VerifyException("Unexpected schemas " + child.prettyTree())); - return; + LOG.warn("{}: unexpected schema {}", deviceId, child.prettyTree()); + return List.of(); } // FIXME: we are producing the wrong thing here and simply not handling all the use cases @@ -274,15 +278,19 @@ public final class NetconfStateSchemas implements NetconfDeviceSchemas { // translating it to the real world -- for example turning a String into a XMLNamespace or a local name. final var builder = ImmutableSet.builderWithExpectedSize(schemaMap.size()); for (var schemaNode : schemaMap.body()) { - final var qname = createFromNormalizedNode(id, schemaNode); + final var qname = createFromNormalizedNode(deviceId, schemaNode); if (qname != null) { builder.add(qname); } } - future.set(new NetconfStateSchemas(builder.build())); + + final var sources = builder.build(); + return sources.isEmpty() ? List.of() : List.of(new ProvidedSources<>(YangTextSource.class, + new MonitoringSchemaSourceProvider(deviceId, deviceRpc), sources)); } - private static @Nullable QName createFromNormalizedNode(final RemoteDeviceId id, final MapEntryNode schemaEntry) { + 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 @@ -367,4 +375,45 @@ public final class NetconfStateSchemas implements NetconfDeviceSchemas { } } } + + // Resolve schemas based on RFC6020's conformance announcement in message, as defined in + // https://www.rfc-editor.org/rfc/rfc6020#section-5.6.4 + private static ListenableFuture resolveYang10(final RemoteDeviceId deviceId, + final NetconfSessionPreferences sessionPreferences, final NetconfRpcService deviceRpc, + final EffectiveModelContext baseModelContext, final List> monitoringSources) { + final var providedSources = monitoringSources.stream() + .flatMap(sources -> sources.sources().stream()) + .collect(Collectors.toSet()); + LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", deviceId, providedSources); + + final var requiredSources = new HashSet<>(sessionPreferences.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: {}", deviceId, requiredSourcesNotProvided); + LOG.warn("{}: Attempting to build schema context from required sources", deviceId); + } + + // 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: {}", deviceId, providedSourcesNotRequired); + LOG.warn("{}: Adding provided but not required sources as required to prevent failures", deviceId); + LOG.debug("{}: Netconf device reported in hello: {}", deviceId, requiredSources); + requiredSources.addAll(providedSourcesNotRequired); + } + + + return Futures.immediateFuture(new NetconfDeviceSchemas(requiredSources, + // FIXME: determine features + FeatureSet.builder().build(), + // FIXME: use this instead of adjusted required sources + Set.of(), + monitoringSources)); + } }