Refactor NetconfDeviceSchemas 63/110363/6
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 26 Feb 2024 16:55:14 +0000 (17:55 +0100)
committerRobert Varga <nite@hq.sk>
Wed, 28 Feb 2024 00:34:08 +0000 (00:34 +0000)
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 <robert.varga@pantheon.tech>
13 files changed:
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java [deleted file]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemas.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/ProvidedSources.java [new file with mode: 0644]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultDeviceNetconfSchemaProvider.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DeviceSources.java [deleted file]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/NetconfStateSchemasResolverImpl.java [moved from plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemas.java with 61% similarity]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetup.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NC881Test.java [moved from plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NC881Test.java with 86% similarity]
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/NetconfStateSchemasTest.java [moved from plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasTest.java with 81% similarity]
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetupTest.java

index 70c661fae322544d56234fcf0ff25c4c9d39ca1d..d23cc333f89d6c1d1d97c51e47d0fc910ef102e5 100644 (file)
@@ -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<QName> 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 (file)
index ca8d8d8..0000000
+++ /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<? extends NetconfDeviceSchemas> 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);
-    }
-}
index 121d0e3996ea52419ba61d169c56d7b949e76f61..8b99ae8475c3abf87573ca4adb22bb26ac7876b7 100644 (file)
@@ -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<QName> 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<QName> requiredSources,
+        FeatureSet features,
+        // FIXME: NETCONF-840: use SourceIdentifier
+        Set<QName> librarySources,
+        List<ProvidedSources<?>> providedSources) {
+    public NetconfDeviceSchemas {
+        requiredSources = Set.copyOf(requiredSources);
+        requireNonNull(features);
+        librarySources = Set.copyOf(librarySources);
+        providedSources = List.copyOf(providedSources);
+    }
 }
index e72dbb5a88cbb70ea28b55fb9f9d85c03ecd2f21..c4a94a6a2d278fb2f1b7c2eba8290f0ea31ea551 100644 (file)
@@ -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<? extends NetconfDeviceSchemas> resolve(NetconfRpcService deviceRpc,
-        NetconfSessionPreferences remoteSessionCapabilities, RemoteDeviceId id, EffectiveModelContext schemaContext);
+    ListenableFuture<NetconfDeviceSchemas> 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 (file)
index 0000000..1469247
--- /dev/null
@@ -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<T extends SourceRepresentation>(
+        Class<T> representation,
+        SchemaSourceProvider<T> provider,
+        // FIXME: NETCONF-840: use SourceIdentifier
+        Set<QName> sources) {
+    public ProvidedSources {
+        representation = requireNonNull(representation);
+        provider = requireNonNull(provider);
+        sources = Set.copyOf(sources);
+    }
+
+    public Stream<Registration> 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)));
+    }
+}
index 9bbf63f550d6724ea5a93efe0642bd52ef9d2606..54c7cce74e022b3574b354d632457ffd0b247967 100644 (file)
@@ -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<DeviceNetconfSchema> 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 (file)
index 39de902..0000000
+++ /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<QName> requiredSources;
-    private final Set<QName> providedSources;
-    private final SchemaSourceProvider<YangTextSource> sourceProvider;
-
-    DeviceSources(final Set<QName> requiredSources, final Set<QName> providedSources,
-            final SchemaSourceProvider<YangTextSource> sourceProvider) {
-        this.requiredSources = requireNonNull(requiredSources);
-        this.providedSources = requireNonNull(providedSources);
-        this.sourceProvider = requireNonNull(sourceProvider);
-    }
-
-    Set<QName> getRequiredSourcesQName() {
-        return requiredSources;
-    }
-
-    Set<QName> getProvidedSourcesQName() {
-        return providedSources;
-    }
-
-    List<SourceIdentifier> getRequiredSources() {
-        return requiredSources.stream().map(DeviceSources::toSourceId).collect(Collectors.toList());
-    }
-
-    List<Registration> 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
@@ -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<QName> availableYangSchemasQNames;
-
-    public NetconfStateSchemas(final Set<QName> availableYangSchemasQNames) {
-        this.availableYangSchemasQNames = ImmutableSet.copyOf(availableYangSchemasQNames);
-    }
-
     @Override
-    public Set<QName> getAvailableYangSchemasQNames() {
-        return availableYangSchemasQNames;
-    }
+    public ListenableFuture<NetconfDeviceSchemas> 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.<ProvidedSources<?>>of());
+
+        final AsyncFunction<List<ProvidedSources<?>>, 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=<date>&module-set-id=<id>
+        //
+        //        and then dispatch to resolveYang11(), which should resolve schemas based on RFC7950's conformance
+        //        announcement via I <hello/> 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=<date>&content-id=<content-id-value>
+        //        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<NetconfStateSchemas> 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.<NetconfStateSchemas>create();
-        Futures.addCallback(deviceRpc.invokeNetconf(Get.QNAME, GET_SCHEMAS_RPC),
-            new FutureCallback<DOMRpcResult>() {
-                @Override
-                public void onSuccess(final DOMRpcResult result) {
-                    onGetSchemasResult(future, id, modelContext, result);
-                }
-
-                @Override
-                public void onFailure(final Throwable cause) {
-                    // debug, because we expect this error to be reported by caller
-                    LOG.debug("{}: Unable to detect available schemas", id, cause);
-                    future.setException(cause);
-                }
-            }, MoreExecutors.directExecutor());
-        return future;
+    private static ListenableFuture<List<ProvidedSources<?>>> 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<NetconfStateSchemas> future, final RemoteDeviceId id,
-            final EffectiveModelContext modelContext, final DOMRpcResult result) {
+    private static List<ProvidedSources<?>> 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<NetconfStateSchemas> future, final RemoteDeviceId id,
-            final ContainerNode schemasNode) {
+    static List<ProvidedSources<?>> 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.<QName>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 <hello/> message, as defined in
+    // https://www.rfc-editor.org/rfc/rfc6020#section-5.6.4
+    private static ListenableFuture<NetconfDeviceSchemas> resolveYang10(final RemoteDeviceId deviceId,
+            final NetconfSessionPreferences sessionPreferences, final NetconfRpcService deviceRpc,
+            final EffectiveModelContext baseModelContext, final List<ProvidedSources<?>> 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));
+    }
 }
index d93460fa4b9d5a74c32948f68f16d5d7c5d9ecc9..98e4f702a5c3aa394c32f72021b6c4ed344fd073 100644 (file)
@@ -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<EffectiveModelContext> {
     private final Set<AvailableCapability> nonModuleBasedCapabilities = new HashSet<>();
     private final Map<QName, FailureReason> unresolvedCapabilites = new HashMap<>();
     private final Set<AvailableCapability> resolvedCapabilities = new HashSet<>();
-
     private final RemoteDeviceId deviceId;
-    private final DeviceSources deviceSources;
-    private final NetconfSessionPreferences remoteSessionCapabilities;
+    private final NetconfDeviceSchemas deviceSchemas;
+    private final Set<QName> deviceRequiredSources;
+    private final NetconfSessionPreferences sessionPreferences;
     private final SchemaRepository repository;
     private final EffectiveModelContextFactory contextFactory;
 
     private Collection<SourceIdentifier> 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<EffectiveModelContext> {
     public void onSuccess(final EffectiveModelContext result) {
         LOG.debug("{}: Schema context built successfully from {}", deviceId, requiredSources);
 
-        final Collection<QName> 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<EffectiveModelContext> {
 
     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<EffectiveModelContext> {
                 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;
index 002435ee71b40a519367bbc237c817b4e941c603..d651c6ee25a347976de2a73db22f33d0623f23f6 100644 (file)
@@ -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.<EffectiveModelContext>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 + "&amp;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() {
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 fb116e42d526fbe9d49464063453839eb7f538b4..6283ad0ddc13d58768d7064d9ba8026e9ffd8b7c 100644 (file)
@@ -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));
 
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 4fb75588b01c48588cda090809266614b44765bc..b3bbe6c3ec4ebdbf7d018e09633f2bf0c211406d 100644 (file)
@@ -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.<NetconfStateSchemas>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<QName> availableYangSchemasQNames(final List<ProvidedSources<?>> providedSources) {
+        return providedSources.stream()
+            .flatMap(sources -> sources.sources().stream())
+            .collect(Collectors.toSet());
+    }
 }
index d2e1be0558302049a533f8b914a415ead4a375e0..92858176faa3da89cef7b1d44bd9dea20fdd0b7f 100644 (file)
@@ -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 + "&amp;revision=" + TEST_REVISION)));