From: Robert Varga Date: Mon, 26 Feb 2024 16:55:14 +0000 (+0100) Subject: Refactor NetconfDeviceSchemas X-Git-Tag: v7.0.0~3 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=e3355e17dd5ebcd3b92f99ff528e61d3d9ef7fe9;p=netconf.git Refactor NetconfDeviceSchemas NetconfDeviceSchemas is currently largely useless, as of its two implementations only one really works. This patch reworks it to be a record holding information about how the EffectiveModelContext is to be assembled and moves processing to NetconfStateSchemasResolverImpl, making it a private detail in process of doing so. The checks for yang-library are disabled and will be re-enabled with a new implementation, which will act as a counter-balance to the current HELLO+Monitoring implementation. JIRA: NETCONF-840 Change-Id: Ie0e46656ae022cffbe6724738bd982d51e3d80da Signed-off-by: Robert Varga --- diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java index 70c661fae3..d23cc333f8 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java @@ -36,7 +36,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import java.util.Set; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.stream.XMLStreamException; @@ -45,7 +44,6 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers; import org.opendaylight.mdsal.dom.api.DOMRpcResult; -import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas; import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService; import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId; import org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil; @@ -91,7 +89,7 @@ import org.xml.sax.SAXException; * Holds URLs with YANG schema resources for all yang modules reported in * ietf-netconf-yang-library/modules-state/modules node. */ -public final class LibraryModulesSchemas implements NetconfDeviceSchemas { +public final class LibraryModulesSchemas { 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( @@ -383,9 +381,4 @@ public final class LibraryModulesSchemas implements NetconfDeviceSchemas { final String valueStr = node.body().toString(); return Strings.isNullOrEmpty(valueStr) ? Optional.empty() : Optional.of(valueStr.trim()); } - - @Override - public Set getAvailableYangSchemasQNames() { - return null; - } } diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java deleted file mode 100644 index ca8d8d8611..0000000000 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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; - -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.NetconfRpcService; -import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences; -import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId; -import org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangModuleInfoImpl; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.QNameModule; -import org.opendaylight.yangtools.yang.common.Revision; -import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; - -/** - * Default implementation resolving schemas QNames from netconf-state or from modules-state. - */ -public final class NetconfStateSchemasResolverImpl implements NetconfDeviceSchemasResolver { - private static final QName RFC8525_YANG_LIBRARY_CAPABILITY = YangModuleInfoImpl.getInstance().getName(); - private static final QName RFC7895_YANG_LIBRARY_CAPABILITY = RFC8525_YANG_LIBRARY_CAPABILITY - .bindTo(QNameModule.create(RFC8525_YANG_LIBRARY_CAPABILITY.getNamespace(), Revision.of("2016-06-21"))).intern(); - - @Override - public ListenableFuture resolve(final NetconfRpcService deviceRpc, - final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id, - final EffectiveModelContext schemaContext) { - // FIXME: I think we should prefer YANG library here - if (remoteSessionCapabilities.isMonitoringSupported()) { - return NetconfStateSchemas.forDevice(deviceRpc, remoteSessionCapabilities, id, schemaContext); - } - if (remoteSessionCapabilities.containsModuleCapability(RFC8525_YANG_LIBRARY_CAPABILITY) - || remoteSessionCapabilities.containsModuleCapability(RFC7895_YANG_LIBRARY_CAPABILITY)) { - return LibraryModulesSchemas.forDevice(deviceRpc, id); - } - - return Futures.immediateFuture(NetconfStateSchemas.EMPTY); - } -} diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemas.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemas.java index 121d0e3996..8b99ae8475 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemas.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemas.java @@ -7,10 +7,38 @@ */ package org.opendaylight.netconf.client.mdsal.api; +import static java.util.Objects.requireNonNull; + +import java.util.List; import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; +import org.opendaylight.yangtools.yang.model.api.source.SourceRepresentation; +import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet; -public interface NetconfDeviceSchemas { - // FIXME: document this method - Set getAvailableYangSchemasQNames(); +/** + * The specification of the {@link SourceRepresentation}s and how they need to be assembled to form the device's + * {@link EffectiveModelContext}. + * + * @param requiredSources the set of sources that are required to form the accurate model of the device + * @param librarySources additional sources that should be presented to YANG parser, typically to resolve submodules + * when the originator of this object is Just Not Sure(tm) + * @param providedSources {@link ProvidedSources} grouped by their representation + */ +// FIXME: this structure will need to be updated for NMDA to support per-datastore model contexts +@NonNullByDefault +public record NetconfDeviceSchemas( + // FIXME: NETCONF-840: use SourceIdentifier + Set requiredSources, + FeatureSet features, + // FIXME: NETCONF-840: use SourceIdentifier + Set librarySources, + List> providedSources) { + public NetconfDeviceSchemas { + requiredSources = Set.copyOf(requiredSources); + requireNonNull(features); + librarySources = Set.copyOf(librarySources); + providedSources = List.copyOf(providedSources); + } } diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java index e72dbb5a88..c4a94a6a2d 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java @@ -11,10 +11,12 @@ import com.google.common.util.concurrent.ListenableFuture; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; /** - * Factory for netconf device schemas. + * A component capable of determining the {@link NetconfDeviceSchemas} for a particular device. */ public interface NetconfDeviceSchemasResolver { // FIXME: document this method - ListenableFuture resolve(NetconfRpcService deviceRpc, - NetconfSessionPreferences remoteSessionCapabilities, RemoteDeviceId id, EffectiveModelContext schemaContext); + ListenableFuture resolve(RemoteDeviceId deviceId, + NetconfSessionPreferences sessionPreferences, NetconfRpcService deviceRpc, + // FIXME: this should not be needed + EffectiveModelContext baseModelContext); } diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/ProvidedSources.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/ProvidedSources.java new file mode 100644 index 0000000000..1469247e53 --- /dev/null +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/ProvidedSources.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 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.api; + +import static java.util.Objects.requireNonNull; + +import java.util.Set; +import java.util.stream.Stream; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.opendaylight.yangtools.concepts.Registration; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier; +import org.opendaylight.yangtools.yang.model.api.source.SourceRepresentation; +import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider; +import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry; + +/** + * A set of sources provided through a single provider. A NETCONF device can have several of these. + * + * @param representation the {@link SourceRepresentation} of provided sources + * @param provider the {@link SchemaSourceProvider} providing the sources + * @param sources provided sources + */ +@NonNullByDefault +public record ProvidedSources( + Class representation, + SchemaSourceProvider provider, + // FIXME: NETCONF-840: use SourceIdentifier + Set sources) { + public ProvidedSources { + representation = requireNonNull(representation); + provider = requireNonNull(provider); + sources = Set.copyOf(sources); + } + + public Stream registerWith(final SchemaSourceRegistry registry, final int cost) { + return sources.stream() + .map(qname -> new SourceIdentifier(qname.getLocalName(), qname.getRevision().orElse(null))) + .map(sourceId -> registry.registerSchemaSource(provider, + PotentialSchemaSource.create(sourceId, representation, cost))); + } +} diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultDeviceNetconfSchemaProvider.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultDeviceNetconfSchemaProvider.java index 9bbf63f550..54c7cce74e 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultDeviceNetconfSchemaProvider.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultDeviceNetconfSchemaProvider.java @@ -10,16 +10,11 @@ package org.opendaylight.netconf.client.mdsal.impl; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Sets; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import java.util.HashSet; import java.util.concurrent.Executor; +import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNull; -import org.opendaylight.netconf.client.mdsal.LibraryModulesSchemas; -import org.opendaylight.netconf.client.mdsal.LibrarySchemaSourceProvider; -import org.opendaylight.netconf.client.mdsal.NetconfStateSchemasResolverImpl; import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchema; import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema; import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider; @@ -31,6 +26,7 @@ import org.opendaylight.yangtools.concepts.Registration; import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory; import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration; import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository; +import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource.Costs; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry; import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository; import org.slf4j.Logger; @@ -41,7 +37,7 @@ public final class DefaultDeviceNetconfSchemaProvider implements DeviceNetconfSc private static final Logger LOG = LoggerFactory.getLogger(DefaultDeviceNetconfSchemaProvider.class); // FIXME: resolver seems to be a useless indirection - private final NetconfDeviceSchemasResolver resolver; + private final NetconfDeviceSchemasResolver resolver = new NetconfStateSchemasResolverImpl(); private final @NonNull EffectiveModelContextFactory contextFactory; private final @NonNull SchemaSourceRegistry registry; private final @NonNull SchemaRepository repository; @@ -49,67 +45,33 @@ public final class DefaultDeviceNetconfSchemaProvider implements DeviceNetconfSc DefaultDeviceNetconfSchemaProvider(final SharedSchemaRepository repository) { this(repository, repository, - repository.createEffectiveModelContextFactory(SchemaContextFactoryConfiguration.getDefault()), - new NetconfStateSchemasResolverImpl()); + repository.createEffectiveModelContextFactory(SchemaContextFactoryConfiguration.getDefault())); } @VisibleForTesting public DefaultDeviceNetconfSchemaProvider(final SchemaSourceRegistry registry, final SchemaRepository repository, - final EffectiveModelContextFactory contextFactory, final NetconfDeviceSchemasResolver resolver) { + final EffectiveModelContextFactory contextFactory) { this.registry = requireNonNull(registry); this.repository = requireNonNull(repository); this.contextFactory = requireNonNull(contextFactory); - this.resolver = requireNonNull(resolver); } @Override public ListenableFuture deviceNetconfSchemaFor(final RemoteDeviceId deviceId, final NetconfSessionPreferences sessionPreferences, final NetconfRpcService deviceRpc, final BaseNetconfSchema baseSchema, final Executor processingExecutor) { - - // Acquire schemas - final var futureSchemas = resolver.resolve(deviceRpc, sessionPreferences, deviceId, 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: {}", 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); - } - - // FIXME: this instanceof check is quite bad - final var sourceProvider = availableSchemas instanceof LibraryModulesSchemas libraryModule - ? new LibrarySchemaSourceProvider(libraryModule.getAvailableModels()) - : new MonitoringSchemaSourceProvider(deviceId, deviceRpc); - return new DeviceSources(requiredSources, providedSources, sourceProvider); - }, MoreExecutors.directExecutor()); + // Acquire sources + final var sourceResolverFuture = resolver.resolve(deviceId, sessionPreferences, deviceRpc, + baseSchema.modelContext()); // Set up the EffectiveModelContext for the device return Futures.transformAsync(sourceResolverFuture, deviceSources -> { LOG.debug("{}: Resolved device sources to {}", deviceId, deviceSources); // Register all sources with repository and start resolution - final var registrations = deviceSources.register(registry); + final var registrations = deviceSources.providedSources().stream() + .flatMap(sources -> sources.registerWith(registry, Costs.REMOTE_IO.getValue())) + .collect(Collectors.toUnmodifiableList()); final var future = new SchemaSetup(repository, contextFactory, deviceId, deviceSources, sessionPreferences) .startResolution(); diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DeviceSources.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DeviceSources.java deleted file mode 100644 index 39de902c51..0000000000 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DeviceSources.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.impl; - -import static java.util.Objects.requireNonNull; - -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import org.opendaylight.yangtools.concepts.Registration; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.Revision; -import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier; -import org.opendaylight.yangtools.yang.model.api.source.YangTextSource; -import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource; -import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider; -import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry; - -/** - * Contains RequiredSources - sources from capabilities. - */ -final class DeviceSources { - private final Set requiredSources; - private final Set providedSources; - private final SchemaSourceProvider sourceProvider; - - DeviceSources(final Set requiredSources, final Set providedSources, - final SchemaSourceProvider sourceProvider) { - this.requiredSources = requireNonNull(requiredSources); - this.providedSources = requireNonNull(providedSources); - this.sourceProvider = requireNonNull(sourceProvider); - } - - Set getRequiredSourcesQName() { - return requiredSources; - } - - Set getProvidedSourcesQName() { - return providedSources; - } - - List getRequiredSources() { - return requiredSources.stream().map(DeviceSources::toSourceId).collect(Collectors.toList()); - } - - List register(final SchemaSourceRegistry schemaRegistry) { - return providedSources.stream() - .map(DeviceSources::toSourceId) - .map(sourceId -> schemaRegistry.registerSchemaSource(sourceProvider, - PotentialSchemaSource.create(sourceId, YangTextSource.class, - PotentialSchemaSource.Costs.REMOTE_IO.getValue()))) - .collect(Collectors.toUnmodifiableList()); - } - - private static SourceIdentifier toSourceId(final QName input) { - return new SourceIdentifier(input.getLocalName(), input.getRevision().map(Revision::toString).orElse(null)); - } -} \ No newline at end of file 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)); + } } diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetup.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetup.java index d93460fa4b..98e4f702a5 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetup.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetup.java @@ -34,6 +34,7 @@ import org.opendaylight.netconf.api.CapabilityURN; import org.opendaylight.netconf.client.mdsal.NetconfDevice.EmptySchemaContextException; import org.opendaylight.netconf.client.mdsal.NetconfDeviceCapabilities; import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema; +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.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability; @@ -61,38 +62,40 @@ final class SchemaSetup implements FutureCallback { private final Set nonModuleBasedCapabilities = new HashSet<>(); private final Map unresolvedCapabilites = new HashMap<>(); private final Set resolvedCapabilities = new HashSet<>(); - private final RemoteDeviceId deviceId; - private final DeviceSources deviceSources; - private final NetconfSessionPreferences remoteSessionCapabilities; + private final NetconfDeviceSchemas deviceSchemas; + private final Set deviceRequiredSources; + private final NetconfSessionPreferences sessionPreferences; private final SchemaRepository repository; private final EffectiveModelContextFactory contextFactory; private Collection requiredSources; SchemaSetup(final SchemaRepository repository, final EffectiveModelContextFactory contextFactory, - final RemoteDeviceId deviceId, final DeviceSources deviceSources, - final NetconfSessionPreferences remoteSessionCapabilities) { + final RemoteDeviceId deviceId, final NetconfDeviceSchemas deviceSchemas, + final NetconfSessionPreferences sessionPreferences) { this.repository = requireNonNull(repository); this.contextFactory = requireNonNull(contextFactory); this.deviceId = requireNonNull(deviceId); - this.deviceSources = requireNonNull(deviceSources); - this.remoteSessionCapabilities = requireNonNull(remoteSessionCapabilities); + this.deviceSchemas = requireNonNull(deviceSchemas); + this.sessionPreferences = requireNonNull(sessionPreferences); // If device supports notifications and does not contain necessary modules, add them automatically - if (remoteSessionCapabilities.containsNonModuleCapability(CapabilityURN.NOTIFICATION)) { - // FIXME: mutable collection modification! - deviceSources.getRequiredSourcesQName().addAll(List.of( + deviceRequiredSources = new HashSet<>(deviceSchemas.requiredSources()); + if (sessionPreferences.containsNonModuleCapability(CapabilityURN.NOTIFICATION)) { + deviceRequiredSources.add( org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714 - .YangModuleInfoImpl.getInstance().getName(), + .YangModuleInfoImpl.getInstance().getName()); + deviceRequiredSources.add( org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715 - .YangModuleInfoImpl.getInstance().getName()) - ); + .YangModuleInfoImpl.getInstance().getName()); } - requiredSources = deviceSources.getRequiredSources(); - final var missingSources = filterMissingSources(requiredSources); + requiredSources = deviceRequiredSources.stream() + .map(qname -> new SourceIdentifier(qname.getLocalName(), qname.getRevision().orElse(null))) + .collect(Collectors.toList()); + final var missingSources = filterMissingSources(requiredSources); addUnresolvedCapabilities(getQNameFromSourceIdentifiers(missingSources), UnavailableCapability.FailureReason.MissingSource); requiredSources.removeAll(missingSources); @@ -107,23 +110,21 @@ final class SchemaSetup implements FutureCallback { public void onSuccess(final EffectiveModelContext result) { LOG.debug("{}: Schema context built successfully from {}", deviceId, requiredSources); - final Collection filteredQNames = Sets.difference(deviceSources.getRequiredSourcesQName(), - unresolvedCapabilites.keySet()); + final var filteredQNames = Sets.difference(deviceRequiredSources, unresolvedCapabilites.keySet()); resolvedCapabilities.addAll(filteredQNames.stream() .map(capability -> new AvailableCapabilityBuilder() .setCapability(capability.toString()) - .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability)) + .setCapabilityOrigin(sessionPreferences.capabilityOrigin(capability)) .build()) .collect(Collectors.toList())); - nonModuleBasedCapabilities.addAll(remoteSessionCapabilities.nonModuleCaps().keySet().stream() + nonModuleBasedCapabilities.addAll(sessionPreferences.nonModuleCaps().keySet().stream() .map(capability -> new AvailableCapabilityBuilder() .setCapability(capability) - .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability)) + .setCapabilityOrigin(sessionPreferences.capabilityOrigin(capability)) .build()) .collect(Collectors.toList())); - resultFuture.set(new DeviceNetconfSchema(new NetconfDeviceCapabilities( ImmutableMap.copyOf(unresolvedCapabilites), ImmutableSet.copyOf(resolvedCapabilities), ImmutableSet.copyOf(nonModuleBasedCapabilities)), result)); @@ -242,7 +243,7 @@ final class SchemaSetup implements FutureCallback { private QName getQNameFromSourceIdentifier(final SourceIdentifier identifier) { // Required sources are all required and provided merged in DeviceSourcesResolver - for (final QName qname : deviceSources.getRequiredSourcesQName()) { + for (final QName qname : deviceRequiredSources) { if (!qname.getLocalName().equals(identifier.name().getLocalName())) { continue; } @@ -251,8 +252,8 @@ final class SchemaSetup implements FutureCallback { return qname; } } - LOG.warn("Unable to map identifier to a devices reported capability: {} Available: {}",identifier, - deviceSources.getRequiredSourcesQName()); + LOG.warn("Unable to map identifier to a devices reported capability: {} Available: {}", identifier, + deviceRequiredSources); // return null since we cannot find the QName, // this capability will be removed from required sources and not reported as unresolved-capability return null; diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java index 002435ee71..d651c6ee25 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java @@ -49,10 +49,12 @@ import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler; import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId; import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices; import org.opendaylight.netconf.client.mdsal.impl.DefaultDeviceNetconfSchemaProvider; +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.netconf.monitoring.rev101004.NetconfState; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier; import org.opendaylight.yangtools.yang.model.api.source.YangTextSource; @@ -166,11 +168,11 @@ public class NetconfDeviceTest extends AbstractTestModelTest { final var facade = getFacade(); final var listener = getListener(); - final var deviceSchemaProvider = mockDeviceNetconfSchemaProvider(); + doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any(), eq(Get.QNAME)); final var device = new NetconfDeviceBuilder() .setReconnectOnSchemasChange(true) - .setDeviceSchemaProvider(deviceSchemaProvider) + .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider()) .setProcessingExecutor(MoreExecutors.directExecutor()) .setId(getId()) .setSalFacade(facade) @@ -226,6 +228,9 @@ public class NetconfDeviceTest extends AbstractTestModelTest { final var schemaFuture = SettableFuture.create(); doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection()); + final var listener = getListener(); + doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any(), eq(Get.QNAME)); + final var device = new NetconfDeviceBuilder() .setReconnectOnSchemasChange(true) .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(), @@ -238,7 +243,6 @@ public class NetconfDeviceTest extends AbstractTestModelTest { final var sessionCaps = getSessionCaps(true, TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION); - final NetconfDeviceCommunicator listener = getListener(); // session up, start schema resolution device.onRemoteSessionUp(sessionCaps, listener); // session down @@ -259,6 +263,7 @@ public class NetconfDeviceTest extends AbstractTestModelTest { public void testNetconfDeviceAvailableCapabilitiesBuilding() throws Exception { final var facade = getFacade(); final var listener = getListener(); + doReturn(RpcResultBuilder.failed().buildFuture()).when(listener).sendRequest(any(), eq(Get.QNAME)); final var netconfSpy = spy(new NetconfDeviceBuilder() .setReconnectOnSchemasChange(true) @@ -382,8 +387,7 @@ public class NetconfDeviceTest extends AbstractTestModelTest { private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider(final SchemaRepository schemaRepository, final EffectiveModelContextFactory schemaFactory) { - return new DefaultDeviceNetconfSchemaProvider(schemaRegistry, schemaRepository, schemaFactory, - (unused1, unused2, unused3, unused4) -> Futures.immediateFuture(NetconfStateSchemas.EMPTY)); + return new DefaultDeviceNetconfSchemaProvider(schemaRegistry, schemaRepository, schemaFactory); } public RemoteDeviceId getId() { diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NC881Test.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NC881Test.java similarity index 86% rename from plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NC881Test.java rename to plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NC881Test.java index fb116e42d5..6283ad0ddc 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NC881Test.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NC881Test.java @@ -5,7 +5,7 @@ * 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.junit.jupiter.api.Assertions.assertFalse; @@ -21,7 +21,8 @@ class NC881Test { final var expected = XmlUtil.readXmlToDocument( NC881Test.class.getResourceAsStream("/nc881/netconf-state-filtered.xml")); - final var filteredDom = NetconfStateSchemas.ietfMonitoringCopy(new DOMSource(source.getDocumentElement())); + final var filteredDom = NetconfStateSchemasResolverImpl.ietfMonitoringCopy( + new DOMSource(source.getDocumentElement())); final var filtered = XmlUtil.newDocument(); filtered.appendChild(filtered.importNode(filteredDom.getNode(), true)); diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NetconfStateSchemasTest.java similarity index 81% rename from plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasTest.java rename to plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NetconfStateSchemasTest.java index 4fb75588b0..b3bbe6c3ec 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasTest.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NetconfStateSchemasTest.java @@ -5,24 +5,23 @@ * 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.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; 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 com.google.common.util.concurrent.Futures; -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.stream.Collectors; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -31,17 +30,18 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException; import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult; +import org.opendaylight.netconf.client.mdsal.AbstractBaseSchemasTest; +import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas; 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.client.mdsal.impl.NetconfMessageTransformUtil; 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.netconf.monitoring.rev101004.NetconfState; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.Schemas; 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.RpcResultBuilder; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; @@ -79,12 +79,12 @@ public class NetconfStateSchemasTest extends AbstractBaseSchemasTest { } @Test - public void testCreate() throws Exception { - final var future = SettableFuture.create(); - NetconfStateSchemas.create(future, DEVICE_ID, SCHEMAS_PAYLOAD); - final var schemas = Futures.getDone(future); + public void testTesolveMonitoringSources() throws Exception { + final var providedSchemas = NetconfStateSchemasResolverImpl.resolveMonitoringSources(DEVICE_ID, rpc, + SCHEMAS_PAYLOAD); + + final var availableYangSchemasQNames = availableYangSchemasQNames(providedSchemas); - final var availableYangSchemasQNames = schemas.getAvailableYangSchemasQNames(); assertEquals(69, availableYangSchemasQNames.size()); assertThat(availableYangSchemasQNames, @@ -107,7 +107,7 @@ public class NetconfStateSchemasTest extends AbstractBaseSchemasTest { doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcReply))).when(rpc) .invokeNetconf(eq(Get.QNAME), any()); final var stateSchemas = assertSchemas(CAPS); - final var availableYangSchemasQNames = stateSchemas.getAvailableYangSchemasQNames(); + final var availableYangSchemasQNames = availableYangSchemasQNames(stateSchemas.providedSources()); assertEquals(69, availableYangSchemasQNames.size()); assertThat(availableYangSchemasQNames, @@ -117,7 +117,7 @@ public class NetconfStateSchemasTest extends AbstractBaseSchemasTest { @Test public void testCreateMonitoringNotSupported() throws Exception { final var stateSchemas = assertSchemas(NetconfSessionPreferences.fromStrings(Set.of())); - assertEquals(Set.of(), stateSchemas.getAvailableYangSchemasQNames()); + assertEquals(Set.of(), availableYangSchemasQNames(stateSchemas.providedSources())); } @Test @@ -133,20 +133,26 @@ public class NetconfStateSchemasTest extends AbstractBaseSchemasTest { doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcError))).when(rpc) .invokeNetconf(eq(Get.QNAME), any()); - final var ex = assertInstanceOf(OperationFailedException.class, assertSchemasFailure()); - assertEquals(List.of(rpcError), ex.getErrorList()); + final var stateSchemas = assertSchemas(CAPS); + assertEquals(List.of(), stateSchemas.providedSources()); } - private NetconfStateSchemas assertSchemas(final NetconfSessionPreferences prefs) { + private NetconfDeviceSchemas assertSchemas(final NetconfSessionPreferences prefs) { try { - return Futures.getDone(NetconfStateSchemas.forDevice(rpc, prefs, DEVICE_ID, MODEL_CONTEXT)); + return Futures.getDone(new NetconfStateSchemasResolverImpl().resolve(DEVICE_ID, prefs, rpc, MODEL_CONTEXT)); } catch (ExecutionException e) { throw new AssertionError(e); } } private Throwable assertSchemasFailure() { - final var future = NetconfStateSchemas.forDevice(rpc, CAPS, DEVICE_ID, MODEL_CONTEXT); + final var future = new NetconfStateSchemasResolverImpl().resolve(DEVICE_ID, CAPS, rpc, MODEL_CONTEXT); return assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause(); } + + private static Set availableYangSchemasQNames(final List> providedSources) { + return providedSources.stream() + .flatMap(sources -> sources.sources().stream()) + .collect(Collectors.toSet()); + } } diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetupTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetupTest.java index d2e1be0558..92858176fa 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetupTest.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetupTest.java @@ -28,13 +28,16 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.opendaylight.netconf.client.mdsal.AbstractTestModelTest; +import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas; 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.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier; import org.opendaylight.yangtools.yang.model.api.source.YangTextSource; +import org.opendaylight.yangtools.yang.model.api.stmt.FeatureSet; import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory; import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException; import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository; @@ -72,7 +75,8 @@ class SchemaSetupTest extends AbstractTestModelTest { .getSchemaSource(any(), eq(YangTextSource.class)); final var setup = new SchemaSetup(schemaRepository, contextFactory, DEVICE_ID, - new DeviceSources(Set.of(TEST_QNAME, TEST_QNAME2), Set.of(TEST_QNAME, TEST_QNAME2), sourceProvider), + new NetconfDeviceSchemas(Set.of(TEST_QNAME, TEST_QNAME2), FeatureSet.builder().build(), Set.of(), + List.of(new ProvidedSources<>(YangTextSource.class, sourceProvider, Set.of(TEST_QNAME, TEST_QNAME2)))), NetconfSessionPreferences.fromStrings(Set.of())); final var result = Futures.getDone(setup.startResolution()); @@ -92,7 +96,8 @@ class SchemaSetupTest extends AbstractTestModelTest { .createEffectiveModelContext(anyCollection()); final var setup = new SchemaSetup(schemaRepository, contextFactory, DEVICE_ID, - new DeviceSources(Set.of(TEST_QNAME, TEST_QNAME2), Set.of(TEST_QNAME, TEST_QNAME2), sourceProvider), + new NetconfDeviceSchemas(Set.of(TEST_QNAME, TEST_QNAME2), FeatureSet.builder().build(), Set.of(), + List.of(new ProvidedSources<>(YangTextSource.class, sourceProvider, Set.of(TEST_QNAME, TEST_QNAME2)))), NetconfSessionPreferences.fromStrings(Set.of())); final var result = Futures.getDone(setup.startResolution()); @@ -110,7 +115,8 @@ class SchemaSetupTest extends AbstractTestModelTest { .createEffectiveModelContext(anyCollection()); final var setup = new SchemaSetup(schemaRepository, contextFactory, DEVICE_ID, - new DeviceSources(Set.of(TEST_QNAME), Set.of(TEST_QNAME), sourceProvider), + new NetconfDeviceSchemas(Set.of(TEST_QNAME), FeatureSet.builder().build(), Set.of(), + List.of(new ProvidedSources<>(YangTextSource.class, sourceProvider, Set.of(TEST_QNAME)))), NetconfSessionPreferences.fromStrings( Set.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION)));