Attempt netconf remount regardless of error-type
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / NetconfDevice.java
index fbe80b373dc3caf11763b280cb7edd5ee44b2056..3746412774bc880109d966225e7dd02292825222 100644 (file)
@@ -7,35 +7,34 @@
  */
 package org.opendaylight.netconf.sal.connect.netconf;
 
+import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
 
-import com.google.common.base.Optional;
-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.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.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.checkerframework.checker.lock.qual.GuardedBy;
+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;
@@ -55,6 +54,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;
@@ -96,6 +97,9 @@ public class NetconfDevice
     private final NetconfDeviceSchemasResolver stateSchemasResolver;
     private final NotificationHandler notificationHandler;
     private final boolean reconnectOnSchemasChange;
+    private final NetconfNode node;
+    private final EventExecutor eventExecutor;
+    private final NetconfNodeAugmentedOptional nodeOptional;
 
     @GuardedBy("this")
     private boolean connected = false;
@@ -109,28 +113,35 @@ public class NetconfDevice
      */
     static NetconfDeviceRpc getRpcForInitialization(final NetconfDeviceCommunicator listener,
                                                     final boolean notificationSupport) {
-        final BaseSchema baseSchema = notificationSupport
-                ? BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS
-                : BaseSchema.BASE_NETCONF_CTX;
+        final BaseSchema baseSchema = resolveBaseSchema(notificationSupport);
 
         return new NetconfDeviceRpc(baseSchema.getSchemaContext(), listener,
                 new NetconfMessageTransformer(baseSchema.getSchemaContext(), false, baseSchema));
     }
 
+    private static BaseSchema resolveBaseSchema(final boolean notificationSupport) {
+        return notificationSupport ? BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS : BaseSchema.BASE_NETCONF_CTX;
+    }
+
     public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id,
                          final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
                          final ListeningExecutorService globalProcessingExecutor,
                          final boolean reconnectOnSchemasChange) {
-        this(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange, null);
+        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 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();
@@ -154,7 +165,8 @@ public class NetconfDevice
         final NetconfDeviceRpc initRpc =
                 getRpcForInitialization(listener, remoteSessionCapabilities.isNotificationsSupported());
         final DeviceSourcesResolver task =
-                new DeviceSourcesResolver(remoteSessionCapabilities, id, stateSchemasResolver, initRpc);
+                new DeviceSourcesResolver(remoteSessionCapabilities, id, stateSchemasResolver, initRpc,
+                        resolveBaseSchema(remoteSessionCapabilities.isNotificationsSupported()).getSchemaContext());
         final ListenableFuture<DeviceSources> sourceResolverFuture = processingExecutor.submit(task);
 
         if (shouldListenOnSchemaChange(remoteSessionCapabilities)) {
@@ -163,7 +175,7 @@ public class NetconfDevice
 
         final FutureCallback<DeviceSources> resolvedSourceCallback = new FutureCallback<DeviceSources>() {
             @Override
-            public void onSuccess(@Nonnull final DeviceSources result) {
+            public void onSuccess(final DeviceSources result) {
                 addProvidedSourcesToSchemaRegistry(result);
                 setUpSchema(result);
             }
@@ -187,7 +199,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 ListenableFuture<DOMRpcResult> rpcResultListenableFuture = deviceRpc.invokeRpc(
                 NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME),
                 NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_CONTENT);
 
@@ -199,7 +211,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);
             }
@@ -239,7 +251,6 @@ public class NetconfDevice
                         ? BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS : BaseSchema.BASE_NETCONF_CTX;
             this.messageTransformer = new NetconfMessageTransformer(result, true, baseSchema);
 
-            updateTransformer(this.messageTransformer);
             // salFacade.onDeviceConnected has to be called before the notification handler is initialized
             this.salFacade.onDeviceConnected(result, remoteSessionCapabilities, deviceRpc,
                     this.deviceActionFactory == null ? null : this.deviceActionFactory.createDeviceAction(
@@ -321,10 +332,10 @@ public class NetconfDevice
                                   final SchemaRepository schemaRepository,
                                   final SchemaContextFactory schemaContextFactory,
                                   final NetconfDeviceSchemasResolver deviceSchemasResolver) {
-            this.schemaRegistry = Preconditions.checkNotNull(schemaRegistry);
-            this.schemaRepository = Preconditions.checkNotNull(schemaRepository);
-            this.schemaContextFactory = Preconditions.checkNotNull(schemaContextFactory);
-            this.stateSchemasResolver = Preconditions.checkNotNull(deviceSchemasResolver);
+            this.schemaRegistry = requireNonNull(schemaRegistry);
+            this.schemaRepository = requireNonNull(schemaRepository);
+            this.schemaContextFactory = requireNonNull(schemaContextFactory);
+            this.stateSchemasResolver = requireNonNull(deviceSchemasResolver);
         }
 
         public SchemaSourceRegistry getSchemaRegistry() {
@@ -353,26 +364,29 @@ public class NetconfDevice
         private final NetconfSessionPreferences remoteSessionCapabilities;
         private final RemoteDeviceId id;
         private final NetconfDeviceSchemasResolver stateSchemasResolver;
+        private final SchemaContext schemaContext;
 
         DeviceSourcesResolver(final NetconfDeviceRpc deviceRpc,
                               final NetconfSessionPreferences remoteSessionCapabilities,
-                              final RemoteDeviceId id, final NetconfDeviceSchemasResolver stateSchemasResolver) {
+                              final RemoteDeviceId id, final NetconfDeviceSchemasResolver stateSchemasResolver,
+                              final SchemaContext schemaContext) {
             this.deviceRpc = deviceRpc;
             this.remoteSessionCapabilities = remoteSessionCapabilities;
             this.id = id;
             this.stateSchemasResolver = stateSchemasResolver;
+            this.schemaContext = schemaContext;
         }
 
         DeviceSourcesResolver(final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id,
                                      final NetconfDeviceSchemasResolver stateSchemasResolver,
-                                     final NetconfDeviceRpc rpcForMonitoring) {
-            this(rpcForMonitoring, remoteSessionCapabilities, id, stateSchemasResolver);
+                                     final NetconfDeviceRpc rpcForMonitoring, final SchemaContext schemaCtx) {
+            this(rpcForMonitoring, remoteSessionCapabilities, id, stateSchemasResolver, schemaCtx);
         }
 
         @Override
         public DeviceSources call() {
             final NetconfDeviceSchemas availableSchemas =
-                    stateSchemasResolver.resolve(deviceRpc, remoteSessionCapabilities, id);
+                    stateSchemasResolver.resolve(deviceRpc, remoteSessionCapabilities, id, schemaContext);
             LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", id,
                     availableSchemas.getAvailableYangSchemasQNames());
 
@@ -547,10 +561,21 @@ 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(
@@ -605,9 +630,8 @@ public class NetconfDevice
         private Collection<SourceIdentifier> stripUnavailableSource(final Collection<SourceIdentifier> requiredSources,
                                                                     final SourceIdentifier sourceIdToRemove) {
             final LinkedList<SourceIdentifier> sourceIdentifiers = Lists.newLinkedList(requiredSources);
-            final boolean removed = sourceIdentifiers.remove(sourceIdToRemove);
-            Preconditions.checkState(
-                    removed, "{}: Trying to remove {} from {} failed", id, sourceIdToRemove, requiredSources);
+            checkState(sourceIdentifiers.remove(sourceIdToRemove),
+                    "%s: Trying to remove %s from %s failed", id, sourceIdToRemove, requiredSources);
             return sourceIdentifiers;
         }