X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=netconf%2Fsal-netconf-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fsal%2Fconnect%2Fnetconf%2FNetconfDevice.java;h=bf7a7931b656de72ec850a601800cbd70bf73702;hb=0cf6d180df77a544362e2480beb4b9119b50a8c6;hp=2a68e0948e12b073757b40d7dd02b91ba4bbab7d;hpb=c8a8964f28273bd54467a707b444ca6528d909bb;p=netconf.git diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java index 2a68e0948e..bf7a7931b6 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java @@ -13,6 +13,8 @@ import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTr import com.google.common.base.Predicates; import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -20,20 +22,19 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; -import io.netty.util.concurrent.EventExecutor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.checkerframework.checker.lock.qual.GuardedBy; +import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.mdsal.dom.api.DOMRpcResult; import org.opendaylight.mdsal.dom.api.DOMRpcService; import org.opendaylight.netconf.api.NetconfMessage; @@ -54,10 +55,11 @@ import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.NetconfMessag import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.notifications.rev120206.NetconfCapabilityChange; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional; -import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapabilityBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.unavailable.capabilities.UnavailableCapability; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.unavailable.capabilities.UnavailableCapability.FailureReason; +import org.opendaylight.yangtools.concepts.Registration; import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext; import org.opendaylight.yangtools.rfc8528.data.util.EmptyMountPointContext; import org.opendaylight.yangtools.rfc8528.model.api.SchemaMountConstants; @@ -75,9 +77,6 @@ import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository; import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException; import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; -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.SchemaSourceRegistration; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -98,7 +97,7 @@ public class NetconfDevice implements RemoteDevice { protected final SchemaSourceRegistry schemaRegistry; protected final SchemaRepository schemaRepository; - protected final List> sourceRegistrations = new ArrayList<>(); + protected final List sourceRegistrations = new ArrayList<>(); private final RemoteDeviceHandler salFacade; private final ListeningExecutorService processingExecutor; @@ -107,9 +106,6 @@ public class NetconfDevice implements RemoteDevice { private final NotificationHandler notificationHandler; private final boolean reconnectOnSchemasChange; private final BaseNetconfSchemas baseSchemas; - private final NetconfNode node; - private final EventExecutor eventExecutor; - private final NetconfNodeAugmentedOptional nodeOptional; @GuardedBy("this") private boolean connected = false; @@ -120,22 +116,17 @@ public class NetconfDevice implements RemoteDevice { public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final BaseNetconfSchemas baseSchemas, final RemoteDeviceId id, final RemoteDeviceHandler salFacade, final ListeningExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange) { - this(schemaResourcesDTO, baseSchemas, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange, null, - null, null, null); + this(schemaResourcesDTO, baseSchemas, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange, null); } public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final BaseNetconfSchemas baseSchemas, final RemoteDeviceId id, final RemoteDeviceHandler salFacade, final ListeningExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange, - final DeviceActionFactory deviceActionFactory, final NetconfNode node, final EventExecutor eventExecutor, - final NetconfNodeAugmentedOptional nodeOptional) { + final DeviceActionFactory deviceActionFactory) { this.baseSchemas = requireNonNull(baseSchemas); this.id = id; this.reconnectOnSchemasChange = reconnectOnSchemasChange; this.deviceActionFactory = deviceActionFactory; - this.node = node; - this.eventExecutor = eventExecutor; - this.nodeOptional = nodeOptional; schemaRegistry = schemaResourcesDTO.getSchemaRegistry(); schemaRepository = schemaResourcesDTO.getSchemaRepository(); schemaContextFactory = schemaResourcesDTO.getSchemaContextFactory(); @@ -167,39 +158,27 @@ public class NetconfDevice implements RemoteDevice { } // Set up the SchemaContext for the device - final ListenableFuture futureSchema = Futures.transformAsync(sourceResolverFuture, + final ListenableFuture futureSchema = Futures.transformAsync(sourceResolverFuture, deviceSources -> assembleSchemaContext(deviceSources, remoteSessionCapabilities), processingExecutor); // Potentially acquire mount point list and interpret it - final ListenableFuture futureContext = Futures.transformAsync(futureSchema, - schemaContext -> createMountPointContext(schemaContext, baseSchema, listener), processingExecutor); + final ListenableFuture futureContext = Futures.transformAsync(futureSchema, + result -> Futures.transform(createMountPointContext(result.modelContext(), baseSchema, listener), + mount -> new NetconfDeviceSchema(result.capabilities(), mount), processingExecutor), + processingExecutor); - Futures.addCallback(futureContext, new FutureCallback() { + Futures.addCallback(futureContext, new FutureCallback<>() { @Override - public void onSuccess(final MountPointContext result) { + public void onSuccess(final NetconfDeviceSchema result) { handleSalInitializationSuccess(result, remoteSessionCapabilities, - getDeviceSpecificRpc(result, listener, baseSchema), listener); + getDeviceSpecificRpc(result.mountContext(), listener, baseSchema), listener); } @Override public void onFailure(final Throwable cause) { LOG.warn("{}: Unexpected error resolving device sources", id, cause); - - // No more sources, fail or try to reconnect - if (cause instanceof EmptySchemaContextException) { - if (nodeOptional != null && nodeOptional.getIgnoreMissingSchemaSources().getAllowed()) { - eventExecutor.schedule(() -> { - LOG.warn("Reconnection is allowed! This can lead to unexpected errors at runtime."); - LOG.warn("{} : No more sources for schema context.", id); - LOG.info("{} : Try to remount device.", id); - onRemoteSessionDown(); - salFacade.onDeviceReconnected(remoteSessionCapabilities, node); - }, nodeOptional.getIgnoreMissingSchemaSources().getReconnectTime().toJava(), - TimeUnit.MILLISECONDS); - return; - } - } - + // FIXME: this causes salFacade to see onDeviceDisconnected() and then onDeviceFailed(), which is quite + // weird handleSalInitializationFailure(cause, listener); salFacade.onDeviceFailed(cause); } @@ -224,9 +203,9 @@ public class NetconfDevice implements RemoteDevice { // Only disconnect is enough, // the reconnecting nature of the connector will take care of reconnecting listener.disconnect(); - return Optional.empty(); + return false; } - return Optional.of(notification); + return true; }); } @@ -242,19 +221,20 @@ public class NetconfDevice implements RemoteDevice { return remoteSessionCapabilities.isNotificationsSupported() && reconnectOnSchemasChange; } - private synchronized void handleSalInitializationSuccess(final MountPointContext result, + private synchronized void handleSalInitializationSuccess(final NetconfDeviceSchema deviceSchema, final NetconfSessionPreferences remoteSessionCapabilities, final DOMRpcService deviceRpc, final RemoteDeviceCommunicator listener) { //NetconfDevice.SchemaSetup can complete after NetconfDeviceCommunicator was closed. In that case do nothing, //since salFacade.onDeviceDisconnected was already called. if (connected) { - messageTransformer = new NetconfMessageTransformer(result, true, + final var mount = deviceSchema.mountContext(); + messageTransformer = new NetconfMessageTransformer(mount, true, resolveBaseSchema(remoteSessionCapabilities.isNotificationsSupported())); // salFacade.onDeviceConnected has to be called before the notification handler is initialized - salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc, + salFacade.onDeviceConnected(deviceSchema, remoteSessionCapabilities, deviceRpc, deviceActionFactory == null ? null : deviceActionFactory.createDeviceAction( - messageTransformer, listener, result.getEffectiveModelContext())); + messageTransformer, listener, mount.getEffectiveModelContext())); notificationHandler.onRemoteSchemaUp(messageTransformer); LOG.info("{}: Netconf connector initialized successfully", id); @@ -285,21 +265,18 @@ public class NetconfDevice implements RemoteDevice { this.connected = connected; } - private ListenableFuture assembleSchemaContext(final DeviceSources deviceSources, + private ListenableFuture assembleSchemaContext(final DeviceSources deviceSources, final NetconfSessionPreferences remoteSessionCapabilities) { LOG.debug("{}: Resolved device sources to {}", id, deviceSources); - final SchemaSourceProvider yangProvider = deviceSources.getSourceProvider(); - for (final SourceIdentifier sourceId : deviceSources.getProvidedSources()) { - sourceRegistrations.add(schemaRegistry.registerSchemaSource(yangProvider, - PotentialSchemaSource.create(sourceId, YangTextSchemaSource.class, - PotentialSchemaSource.Costs.REMOTE_IO.getValue()))); - } + + sourceRegistrations.addAll(deviceSources.register(schemaRegistry)); return new SchemaSetup(deviceSources, remoteSessionCapabilities).startResolution(); } - private ListenableFuture createMountPointContext(final EffectiveModelContext schemaContext, - final BaseSchema baseSchema, final NetconfDeviceCommunicator listener) { + private ListenableFuture<@NonNull MountPointContext> createMountPointContext( + final EffectiveModelContext schemaContext, final BaseSchema baseSchema, + final NetconfDeviceCommunicator listener) { final MountPointContext emptyContext = new EmptyMountPointContext(schemaContext); if (schemaContext.findModule(SchemaMountConstants.RFC8528_MODULE).isEmpty()) { return Futures.immediateFuture(emptyContext); @@ -340,7 +317,7 @@ public class NetconfDevice implements RemoteDevice { notificationHandler.onRemoteSchemaDown(); salFacade.onDeviceDisconnected(); - sourceRegistrations.forEach(SchemaSourceRegistration::close); + sourceRegistrations.forEach(Registration::close); sourceRegistrations.clear(); resetMessageTransformer(); } @@ -403,34 +380,47 @@ public class NetconfDevice implements RemoteDevice { } /** - * A dedicated exception to indicate when we fail to setup a SchemaContext. - * - * @author Robert Varga + * A dedicated exception to indicate when we fail to setup an {@link EffectiveModelContext}. */ - private static final class EmptySchemaContextException extends Exception { + public static final class EmptySchemaContextException extends Exception { private static final long serialVersionUID = 1L; - EmptySchemaContextException(final String message) { + public EmptySchemaContextException(final String message) { super(message); } } + /** + * {@link NetconfDeviceCapabilities} and {@link EffectiveModelContext}. + */ + private record SchemaResult( + @NonNull NetconfDeviceCapabilities capabilities, + @NonNull EffectiveModelContext modelContext) { + + SchemaResult { + requireNonNull(capabilities); + requireNonNull(modelContext); + } + } + /** * Schema builder that tries to build schema context from provided sources or biggest subset of it. */ private final class SchemaSetup implements FutureCallback { - private final SettableFuture resultFuture = SettableFuture.create(); + private final SettableFuture resultFuture = SettableFuture.create(); + + private final Set nonModuleBasedCapabilities = new HashSet<>(); + private final Map unresolvedCapabilites = new HashMap<>(); + private final Set resolvedCapabilities = new HashSet<>(); private final DeviceSources deviceSources; private final NetconfSessionPreferences remoteSessionCapabilities; - private final NetconfDeviceCapabilities capabilities; private Collection requiredSources; SchemaSetup(final DeviceSources deviceSources, final NetconfSessionPreferences remoteSessionCapabilities) { this.deviceSources = deviceSources; this.remoteSessionCapabilities = remoteSessionCapabilities; - capabilities = remoteSessionCapabilities.getNetconfDeviceCapabilities(); // If device supports notifications and does not contain necessary modules, add them automatically if (remoteSessionCapabilities.containsNonModuleCapability( @@ -448,12 +438,12 @@ public class NetconfDevice implements RemoteDevice { requiredSources = deviceSources.getRequiredSources(); final Collection missingSources = filterMissingSources(requiredSources); - capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(missingSources), - UnavailableCapability.FailureReason.MissingSource); + addUnresolvedCapabilities(getQNameFromSourceIdentifiers(missingSources), + UnavailableCapability.FailureReason.MissingSource); requiredSources.removeAll(missingSources); } - ListenableFuture startResolution() { + ListenableFuture startResolution() { trySetupSchema(); return resultFuture; } @@ -463,19 +453,24 @@ public class NetconfDevice implements RemoteDevice { LOG.debug("{}: Schema context built successfully from {}", id, requiredSources); final Collection filteredQNames = Sets.difference(deviceSources.getRequiredSourcesQName(), - capabilities.getUnresolvedCapabilites().keySet()); - capabilities.addCapabilities(filteredQNames.stream().map(entry -> new AvailableCapabilityBuilder() - .setCapability(entry.toString()).setCapabilityOrigin( - remoteSessionCapabilities.getModuleBasedCapsOrigin().get(entry)).build()) - .collect(Collectors.toList())); - - capabilities.addNonModuleBasedCapabilities(remoteSessionCapabilities - .getNonModuleCaps().stream().map(entry -> new AvailableCapabilityBuilder() - .setCapability(entry).setCapabilityOrigin( - remoteSessionCapabilities.getNonModuleBasedCapsOrigin().get(entry)).build()) - .collect(Collectors.toList())); - - resultFuture.set(result); + unresolvedCapabilites.keySet()); + resolvedCapabilities.addAll(filteredQNames.stream() + .map(capability -> new AvailableCapabilityBuilder() + .setCapability(capability.toString()) + .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability)) + .build()) + .collect(Collectors.toList())); + + nonModuleBasedCapabilities.addAll(remoteSessionCapabilities.getNonModuleCaps().stream() + .map(capability -> new AvailableCapabilityBuilder() + .setCapability(capability) + .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability)) + .build()) + .collect(Collectors.toList())); + + + resultFuture.set(new SchemaResult(new NetconfDeviceCapabilities(ImmutableMap.copyOf(unresolvedCapabilites), + ImmutableSet.copyOf(resolvedCapabilities), ImmutableSet.copyOf(nonModuleBasedCapabilities)), result)); } @Override @@ -509,7 +504,7 @@ public class NetconfDevice implements RemoteDevice { } } - private Collection filterMissingSources(final Collection origSources) { + private List filterMissingSources(final Collection origSources) { return origSources.parallelStream().filter(sourceIdentifier -> { try { schemaRepository.getSchemaSource(sourceIdentifier, YangTextSchemaSource.class).get(); @@ -520,7 +515,13 @@ public class NetconfDevice implements RemoteDevice { }).collect(Collectors.toList()); } - private Collection handleMissingSchemaSourceException( + private void addUnresolvedCapabilities(final Collection capabilities, final FailureReason reason) { + for (QName s : capabilities) { + unresolvedCapabilites.put(s, reason); + } + } + + private List handleMissingSchemaSourceException( final MissingSchemaSourceException exception) { // In case source missing, try without it final SourceIdentifier missingSource = exception.getSourceId(); @@ -528,11 +529,9 @@ public class NetconfDevice implements RemoteDevice { id, missingSource); LOG.debug("{}: Unable to build schema context, missing source {}, will reattempt without it", id, missingSource, exception); - final Collection qNameOfMissingSource = - getQNameFromSourceIdentifiers(Sets.newHashSet(missingSource)); + final var qNameOfMissingSource = getQNameFromSourceIdentifiers(Sets.newHashSet(missingSource)); if (!qNameOfMissingSource.isEmpty()) { - capabilities.addUnresolvedCapabilities( - qNameOfMissingSource, UnavailableCapability.FailureReason.MissingSource); + addUnresolvedCapabilities(qNameOfMissingSource, UnavailableCapability.FailureReason.MissingSource); } return stripUnavailableSource(missingSource); } @@ -549,14 +548,13 @@ public class NetconfDevice implements RemoteDevice { id, failedSourceId); LOG.warn("{}: Unable to build schema context, failed to resolve source {}, will reattempt without it", id, failedSourceId, resolutionException); - capabilities.addUnresolvedCapabilities( - getQNameFromSourceIdentifiers(Collections.singleton(failedSourceId)), + addUnresolvedCapabilities(getQNameFromSourceIdentifiers(List.of(failedSourceId)), UnavailableCapability.FailureReason.UnableToResolve); return stripUnavailableSource(resolutionException.getFailedSource()); } // unsatisfied imports - final Set unresolvedSources = resolutionException.getUnsatisfiedImports().keySet(); - capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(unresolvedSources), + addUnresolvedCapabilities( + getQNameFromSourceIdentifiers(resolutionException.getUnsatisfiedImports().keySet()), UnavailableCapability.FailureReason.UnableToResolve); LOG.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only", id, resolutionException.getUnsatisfiedImports()); @@ -565,11 +563,11 @@ public class NetconfDevice implements RemoteDevice { return resolutionException.getResolvedSources(); } - private Collection stripUnavailableSource(final SourceIdentifier sourceIdToRemove) { - final LinkedList sourceIdentifiers = new LinkedList<>(requiredSources); - checkState(sourceIdentifiers.remove(sourceIdToRemove), - "%s: Trying to remove %s from %s failed", id, sourceIdToRemove, requiredSources); - return sourceIdentifiers; + private List stripUnavailableSource(final SourceIdentifier sourceIdToRemove) { + final var tmp = new ArrayList<>(requiredSources); + checkState(tmp.remove(sourceIdToRemove), "%s: Trying to remove %s from %s failed", id, sourceIdToRemove, + requiredSources); + return tmp; } private Collection getQNameFromSourceIdentifiers(final Collection identifiers) {