Fix schema source registrations not being cleared
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / NetconfDevice.java
index 2dc16331726e2857897b372bcd55745e3ad36daa..a6865ba6dd07465b75c91fafb3dffe554033cf26 100644 (file)
@@ -7,34 +7,38 @@
  */
 package org.opendaylight.netconf.sal.connect.netconf;
 
-import com.google.common.base.Optional;
+import static java.util.Objects.requireNonNull;
+
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FluentFuture;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
+import io.netty.util.concurrent.EventExecutor;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
 import javax.annotation.concurrent.GuardedBy;
-import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
-import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
-import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
-import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DOMNotification;
+import org.opendaylight.mdsal.dom.api.DOMRpcResult;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory;
 import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
 import org.opendaylight.netconf.sal.connect.api.NetconfDeviceSchemas;
 import org.opendaylight.netconf.sal.connect.api.NetconfDeviceSchemasResolver;
@@ -52,6 +56,8 @@ 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.AvailableCapabilityBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.unavailable.capabilities.UnavailableCapability;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -61,14 +67,12 @@ import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
 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.SchemaSourceRepresentation;
 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.opendaylight.yangtools.yang.parser.rfc7950.repo.ASTSchemaSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -81,17 +85,22 @@ public class NetconfDevice
     private static final Logger LOG = LoggerFactory.getLogger(NetconfDevice.class);
 
     protected final RemoteDeviceId id;
-    private final boolean reconnectOnSchemasChange;
-
     protected final SchemaContextFactory schemaContextFactory;
-    private final RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
-    private final ListeningExecutorService processingExecutor;
     protected final SchemaSourceRegistry schemaRegistry;
     protected final SchemaRepository schemaRepository;
+
+    protected final List<SchemaSourceRegistration<?>> sourceRegistrations = new ArrayList<>();
+
+    private final RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
+    private final ListeningExecutorService processingExecutor;
+    private final DeviceActionFactory deviceActionFactory;
     private final NetconfDeviceSchemasResolver stateSchemasResolver;
     private final NotificationHandler notificationHandler;
-    protected final List<SchemaSourceRegistration<? extends SchemaSourceRepresentation>> sourceRegistrations =
-            new ArrayList<>();
+    private final boolean reconnectOnSchemasChange;
+    private final NetconfNode node;
+    private final EventExecutor eventExecutor;
+    private final NetconfNodeAugmentedOptional nodeOptional;
+
     @GuardedBy("this")
     private boolean connected = false;
 
@@ -114,15 +123,29 @@ public class NetconfDevice
 
     public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id,
                          final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
-                         final ExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange) {
+                         final ListeningExecutorService globalProcessingExecutor,
+                         final boolean reconnectOnSchemasChange) {
+        this(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange, null, null, null,
+                null);
+    }
+
+    public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id,
+            final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
+            final ListeningExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange,
+            final DeviceActionFactory deviceActionFactory, final NetconfNode node, final EventExecutor eventExecutor,
+            final NetconfNodeAugmentedOptional nodeOptional) {
         this.id = id;
         this.reconnectOnSchemasChange = reconnectOnSchemasChange;
+        this.deviceActionFactory = deviceActionFactory;
+        this.node = node;
+        this.eventExecutor = eventExecutor;
+        this.nodeOptional = nodeOptional;
         this.schemaRegistry = schemaResourcesDTO.getSchemaRegistry();
         this.schemaRepository = schemaResourcesDTO.getSchemaRepository();
         this.schemaContextFactory = schemaResourcesDTO.getSchemaContextFactory();
         this.salFacade = salFacade;
         this.stateSchemasResolver = schemaResourcesDTO.getStateSchemasResolver();
-        this.processingExecutor = MoreExecutors.listeningDecorator(globalProcessingExecutor);
+        this.processingExecutor = requireNonNull(globalProcessingExecutor);
         this.notificationHandler = new NotificationHandler(salFacade, id);
     }
 
@@ -149,7 +172,7 @@ public class NetconfDevice
 
         final FutureCallback<DeviceSources> resolvedSourceCallback = new FutureCallback<DeviceSources>() {
             @Override
-            public void onSuccess(final DeviceSources result) {
+            public void onSuccess(@Nonnull final DeviceSources result) {
                 addProvidedSourcesToSchemaRegistry(result);
                 setUpSchema(result);
             }
@@ -160,7 +183,7 @@ public class NetconfDevice
 
             @Override
             public void onFailure(final Throwable throwable) {
-                LOG.warn("{}: Unexpected error resolving device sources: {}", id, throwable);
+                LOG.warn("{}: Unexpected error resolving device sources", id, throwable);
                 handleSalInitializationFailure(throwable, listener);
             }
         };
@@ -173,7 +196,7 @@ public class NetconfDevice
         // TODO check whether the model describing create subscription is present in schema
         // Perhaps add a default schema context to support create-subscription if the model was not provided
         // (same as what we do for base netconf operations in transformer)
-        final CheckedFuture<DOMRpcResult, DOMRpcException> rpcResultListenableFuture = deviceRpc.invokeRpc(
+        final FluentFuture<DOMRpcResult> rpcResultListenableFuture = deviceRpc.invokeRpc(
                 NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME),
                 NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_CONTENT);
 
@@ -185,7 +208,7 @@ public class NetconfDevice
                     // Only disconnect is enough,
                     // the reconnecting nature of the connector will take care of reconnecting
                     listener.disconnect();
-                    return Optional.absent();
+                    return Optional.empty();
                 }
                 return Optional.of(notification);
             }
@@ -215,19 +238,22 @@ public class NetconfDevice
 
     private synchronized void handleSalInitializationSuccess(final SchemaContext result,
                                         final NetconfSessionPreferences remoteSessionCapabilities,
-                                        final DOMRpcService deviceRpc) {
+                                        final DOMRpcService deviceRpc,
+                                        final RemoteDeviceCommunicator<NetconfMessage> listener) {
         //NetconfDevice.SchemaSetup can complete after NetconfDeviceCommunicator was closed. In that case do nothing,
         //since salFacade.onDeviceDisconnected was already called.
         if (connected) {
             final BaseSchema baseSchema =
                 remoteSessionCapabilities.isNotificationsSupported()
                         ? BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS : BaseSchema.BASE_NETCONF_CTX;
-            messageTransformer = new NetconfMessageTransformer(result, true, baseSchema);
+            this.messageTransformer = new NetconfMessageTransformer(result, true, baseSchema);
 
-            updateTransformer(messageTransformer);
+            updateTransformer(this.messageTransformer);
             // salFacade.onDeviceConnected has to be called before the notification handler is initialized
-            salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc);
-            notificationHandler.onRemoteSchemaUp(messageTransformer);
+            this.salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc,
+                    this.deviceActionFactory == null ? null : this.deviceActionFactory.createDeviceAction(
+                            this.messageTransformer, listener, result));
+            this.notificationHandler.onRemoteSchemaUp(this.messageTransformer);
 
             LOG.info("{}: Netconf connector initialized successfully", id);
         } else {
@@ -250,7 +276,7 @@ public class NetconfDevice
         updateTransformer(null);
     }
 
-    private void updateTransformer(final MessageTransformer<NetconfMessage> transformer) {
+    private synchronized void updateTransformer(final MessageTransformer<NetconfMessage> transformer) {
         messageTransformer = transformer;
     }
 
@@ -273,10 +299,8 @@ public class NetconfDevice
         notificationHandler.onRemoteSchemaDown();
 
         salFacade.onDeviceDisconnected();
-        for (final SchemaSourceRegistration<? extends SchemaSourceRepresentation> sourceRegistration
-                : sourceRegistrations) {
-            sourceRegistration.close();
-        }
+        sourceRegistrations.forEach(SchemaSourceRegistration::close);
+        sourceRegistrations.clear();
         resetMessageTransformer();
     }
 
@@ -353,7 +377,7 @@ public class NetconfDevice
         }
 
         @Override
-        public DeviceSources call() throws Exception {
+        public DeviceSources call() {
             final NetconfDeviceSchemas availableSchemas =
                     stateSchemasResolver.resolve(deviceRpc, remoteSessionCapabilities, id);
             LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", id,
@@ -468,7 +492,7 @@ public class NetconfDevice
         private Collection<SourceIdentifier> filterMissingSources(final Collection<SourceIdentifier> requiredSources) {
             return requiredSources.parallelStream().filter(sourceIdentifier -> {
                 try {
-                    schemaRepository.getSchemaSource(sourceIdentifier, ASTSchemaSource.class).get();
+                    schemaRepository.getSchemaSource(sourceIdentifier, YangTextSchemaSource.class).get();
                     return false;
                 } catch (InterruptedException | ExecutionException e) {
                     return true;
@@ -478,6 +502,8 @@ public class NetconfDevice
 
         /**
          * Build schema context, in case of success or final failure notify device.
+         *
+         * @param requiredSources required sources
          */
         @SuppressWarnings("checkstyle:IllegalCatch")
         private void setUpSchema(Collection<SourceIdentifier> requiredSources) {
@@ -501,7 +527,8 @@ public class NetconfDevice
                                             remoteSessionCapabilities.getNonModuleBasedCapsOrigin().get(entry)).build())
                             .collect(Collectors.toList()));
 
-                    handleSalInitializationSuccess(result, remoteSessionCapabilities, getDeviceSpecificRpc(result));
+                    handleSalInitializationSuccess(result, remoteSessionCapabilities, getDeviceSpecificRpc(result),
+                            listener);
                     return;
                 } catch (final ExecutionException e) {
                     // schemaBuilderFuture.checkedGet() throws only SchemaResolutionException
@@ -511,7 +538,7 @@ public class NetconfDevice
 
                     if (cause instanceof MissingSchemaSourceException) {
                         requiredSources = handleMissingSchemaSourceException(
-                                requiredSources, (MissingSchemaSourceException) e.getCause());
+                                requiredSources, (MissingSchemaSourceException) cause);
                         continue;
                     }
                     if (cause instanceof SchemaResolutionException) {
@@ -527,22 +554,33 @@ public class NetconfDevice
                     return;
                 }
             }
-            // No more sources, fail
-            final IllegalStateException cause = new IllegalStateException(id + ": No more sources for schema context");
-            handleSalInitializationFailure(cause, listener);
-            salFacade.onDeviceFailed(cause);
+            // No more sources, fail or try to reconnect
+            if (nodeOptional != null && nodeOptional.getIgnoreMissingSchemaSources().isAllowed()) {
+                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(), TimeUnit.MILLISECONDS);
+            } else {
+                final IllegalStateException cause =
+                        new IllegalStateException(id + ": No more sources for schema context");
+                handleSalInitializationFailure(cause, listener);
+                salFacade.onDeviceFailed(cause);
+            }
         }
 
         private Collection<SourceIdentifier> handleMissingSchemaSourceException(
                 final Collection<SourceIdentifier> requiredSources, final MissingSchemaSourceException exception) {
             // In case source missing, try without it
             final SourceIdentifier missingSource = exception.getSourceId();
-            LOG.warn("{}: Unable to build schema context, missing source {}, will reattempt without it", id,
-                    missingSource);
+            LOG.warn("{}: Unable to build schema context, missing source {}, will reattempt without it",
+                id, missingSource);
             LOG.debug("{}: Unable to build schema context, missing source {}, will reattempt without it",
-                    exception);
+                id, missingSource, exception);
             final Collection<QName> qNameOfMissingSource =
-                    getQNameFromSourceIdentifiers(Sets.newHashSet(missingSource));
+                getQNameFromSourceIdentifiers(Sets.newHashSet(missingSource));
             if (!qNameOfMissingSource.isEmpty()) {
                 capabilities.addUnresolvedCapabilities(
                         qNameOfMissingSource, UnavailableCapability.FailureReason.MissingSource);
@@ -559,21 +597,22 @@ public class NetconfDevice
                 // flawed model - exclude it
                 final SourceIdentifier failedSourceId = resolutionException.getFailedSource();
                 LOG.warn("{}: Unable to build schema context, failed to resolve source {}, will reattempt without it",
-                        id, failedSourceId);
+                    id, failedSourceId);
                 LOG.warn("{}: Unable to build schema context, failed to resolve source {}, will reattempt without it",
-                        id, resolutionException);
-                capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(
-                        Collections.singleton(failedSourceId)), UnavailableCapability.FailureReason.UnableToResolve);
+                    id, failedSourceId, resolutionException);
+                capabilities.addUnresolvedCapabilities(
+                        getQNameFromSourceIdentifiers(Collections.singleton(failedSourceId)),
+                        UnavailableCapability.FailureReason.UnableToResolve);
                 return stripUnavailableSource(requiredSources, resolutionException.getFailedSource());
             }
             // unsatisfied imports
             final Set<SourceIdentifier> unresolvedSources = resolutionException.getUnsatisfiedImports().keySet();
-            capabilities.addUnresolvedCapabilities(
-                getQNameFromSourceIdentifiers(unresolvedSources), UnavailableCapability.FailureReason.UnableToResolve);
+            capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(unresolvedSources),
+                UnavailableCapability.FailureReason.UnableToResolve);
             LOG.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
-                    id, resolutionException.getUnsatisfiedImports());
+                id, resolutionException.getUnsatisfiedImports());
             LOG.debug("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
-                    resolutionException);
+                id, resolutionException.getUnsatisfiedImports(), resolutionException);
             return resolutionException.getResolvedSources();
         }