Remove CachedYangTextSchemaSource
[netconf.git] / plugins / netconf-client-mdsal / src / main / java / org / opendaylight / netconf / client / mdsal / impl / DefaultDeviceNetconfSchemaProvider.java
1 /*
2  * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.netconf.client.mdsal.impl;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.collect.Sets;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import com.google.common.util.concurrent.MoreExecutors;
17 import java.util.HashSet;
18 import java.util.concurrent.Executor;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.opendaylight.netconf.client.mdsal.LibraryModulesSchemas;
21 import org.opendaylight.netconf.client.mdsal.LibrarySchemaSourceProvider;
22 import org.opendaylight.netconf.client.mdsal.MonitoringSchemaSourceProvider;
23 import org.opendaylight.netconf.client.mdsal.NetconfStateSchemasResolverImpl;
24 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchema;
25 import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema;
26 import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
27 import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
28 import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
29 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
30 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
31 import org.opendaylight.yangtools.concepts.Registration;
32 import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
33 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration;
34 import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
35 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
36 import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 @VisibleForTesting
41 public final class DefaultDeviceNetconfSchemaProvider implements DeviceNetconfSchemaProvider {
42     private static final Logger LOG = LoggerFactory.getLogger(DefaultDeviceNetconfSchemaProvider.class);
43
44     // FIXME: resolver seems to be a useless indirection
45     private final NetconfDeviceSchemasResolver resolver;
46     private final @NonNull EffectiveModelContextFactory contextFactory;
47     private final @NonNull SchemaSourceRegistry registry;
48     private final @NonNull SchemaRepository repository;
49     // FIXME: private final Executor processingExecutor;
50
51     DefaultDeviceNetconfSchemaProvider(final SharedSchemaRepository repository) {
52         this(repository, repository,
53             repository.createEffectiveModelContextFactory(SchemaContextFactoryConfiguration.getDefault()),
54             new NetconfStateSchemasResolverImpl());
55     }
56
57     @VisibleForTesting
58     public DefaultDeviceNetconfSchemaProvider(final SchemaSourceRegistry registry, final SchemaRepository repository,
59             final EffectiveModelContextFactory contextFactory, final NetconfDeviceSchemasResolver resolver) {
60         this.registry = requireNonNull(registry);
61         this.repository = requireNonNull(repository);
62         this.contextFactory = requireNonNull(contextFactory);
63         this.resolver = requireNonNull(resolver);
64     }
65
66     @Override
67     public ListenableFuture<DeviceNetconfSchema> deviceNetconfSchemaFor(final RemoteDeviceId deviceId,
68             final NetconfSessionPreferences sessionPreferences, final NetconfRpcService deviceRpc,
69             final BaseNetconfSchema baseSchema, final Executor processingExecutor) {
70
71         // Acquire schemas
72         final var futureSchemas = resolver.resolve(deviceRpc, sessionPreferences, deviceId, baseSchema.modelContext());
73
74         // Convert to sources
75         final var sourceResolverFuture = Futures.transform(futureSchemas, availableSchemas -> {
76             final var providedSources = availableSchemas.getAvailableYangSchemasQNames();
77             LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", deviceId, providedSources);
78
79             final var requiredSources = new HashSet<>(sessionPreferences.moduleBasedCaps().keySet());
80             final var requiredSourcesNotProvided = Sets.difference(requiredSources, providedSources);
81             if (!requiredSourcesNotProvided.isEmpty()) {
82                 LOG.warn("{}: Netconf device does not provide all yang models reported in hello message capabilities,"
83                         + " required but not provided: {}", deviceId, requiredSourcesNotProvided);
84                 LOG.warn("{}: Attempting to build schema context from required sources", deviceId);
85             }
86
87             // Here all the sources reported in netconf monitoring are merged with those reported in hello.
88             // It is necessary to perform this since submodules are not mentioned in hello but still required.
89             // This clashes with the option of a user to specify supported yang models manually in configuration
90             // for netconf-connector and as a result one is not able to fully override yang models of a device.
91             // It is only possible to add additional models.
92             final var providedSourcesNotRequired = Sets.difference(providedSources, requiredSources);
93             if (!providedSourcesNotRequired.isEmpty()) {
94                 LOG.warn("{}: Netconf device provides additional yang models not reported in "
95                         + "hello message capabilities: {}", deviceId, providedSourcesNotRequired);
96                 LOG.warn("{}: Adding provided but not required sources as required to prevent failures", deviceId);
97                 LOG.debug("{}: Netconf device reported in hello: {}", deviceId, requiredSources);
98                 requiredSources.addAll(providedSourcesNotRequired);
99             }
100
101             // FIXME: this instanceof check is quite bad
102             final var sourceProvider = availableSchemas instanceof LibraryModulesSchemas libraryModule
103                 ? new LibrarySchemaSourceProvider(libraryModule.getAvailableModels())
104                     : new MonitoringSchemaSourceProvider(deviceId, deviceRpc);
105             return new DeviceSources(requiredSources, providedSources, sourceProvider);
106         }, MoreExecutors.directExecutor());
107
108         // Set up the EffectiveModelContext for the device
109         return Futures.transformAsync(sourceResolverFuture, deviceSources -> {
110             LOG.debug("{}: Resolved device sources to {}", deviceId, deviceSources);
111
112             // Register all sources with repository and start resolution
113             final var registrations = deviceSources.register(registry);
114             final var future = new SchemaSetup(repository, contextFactory, deviceId, deviceSources, sessionPreferences)
115                 .startResolution();
116
117             // Unregister sources once resolution is complete
118             future.addListener(() -> registrations.forEach(Registration::close), processingExecutor);
119
120             return future;
121         }, processingExecutor);
122     }
123
124     @Deprecated
125     @Override
126     public SchemaRepository repository() {
127         return repository;
128     }
129
130     @Deprecated
131     @Override
132     public SchemaSourceRegistry registry() {
133         return registry;
134     }
135
136     @Deprecated
137     @Override
138     public EffectiveModelContextFactory contextFactory() {
139         return contextFactory;
140     }
141 }