Attempt netconf remount regardless of error-type 00/84500/1
authorJakub Toth <jtoth@luminanetworks.com>
Mon, 24 Jun 2019 09:17:16 +0000 (05:17 -0400)
committerJakub Toth <jtoth@luminanetworks.com>
Mon, 16 Sep 2019 08:59:49 +0000 (01:59 -0700)
Even if there's a 'no more sources' for schema context, go through
normal remount cycle. It's added a new optional boolean to the netconf
mount fields specifically for this, default to false/ current behavior.

JIRA: NETCONF-611
Change-Id: If983ef0dded606fb75b5e41236c9d0f8328ab7c6
Signed-off-by: Jakub Toth <jtoth@luminanetworks.com>
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceTopologyAdapter.java
netconf/sal-netconf-connector/src/main/yang/netconf-node-optional.yang [new file with mode: 0644]

index a625dd3a49bd3e7077b0813bd9b06b7d899a3843..cb96ffb405f1fffd302d5d955b8a5f6fd359f850 100644 (file)
@@ -64,6 +64,7 @@ import org.opendaylight.netconf.topology.api.NetconfTopology;
 import org.opendaylight.netconf.topology.api.SchemaRepositoryProvider;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
+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.parameters.Protocol;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
@@ -260,12 +261,13 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
     protected ListenableFuture<NetconfDeviceCapabilities> setupConnection(final NodeId nodeId,
                                                                           final Node configNode) {
         final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
+        final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
 
         Preconditions.checkNotNull(netconfNode.getHost());
         Preconditions.checkNotNull(netconfNode.getPort());
         Preconditions.checkNotNull(netconfNode.isTcpOnly());
 
-        final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode);
+        final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
         final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
         final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
         final NetconfReconnectingClientConfiguration clientConfig =
@@ -292,6 +294,11 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
     }
 
     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
+        return createDeviceCommunicator(nodeId, node, null);
+    }
+
+    protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
+            final NetconfNodeAugmentedOptional nodeOptional) {
         //setup default values since default value is not supported in mdsal
         final long defaultRequestTimeoutMillis = node.getDefaultRequestTimeoutMillis() == null
                 ? DEFAULT_REQUEST_TIMEOUT_MILLIS : node.getDefaultRequestTimeoutMillis();
@@ -352,7 +359,10 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
                     .setSchemaResourcesDTO(schemaResourcesDTO)
                     .setGlobalProcessingExecutor(this.processingExecutor)
                     .setId(remoteDeviceId)
-                    .setSalFacade(salFacade);
+                    .setSalFacade(salFacade)
+                    .setNode(node)
+                    .setEventExecutor(eventExecutor)
+                    .setNodeOptional(nodeOptional);
             if (this.deviceActionFactory != null) {
                 netconfDeviceBuilder.setDeviceActionFactory(this.deviceActionFactory);
             }
index f762b889a99f0e910b6353e12292c819e87ceacb..9ab98f6428dc063c074bedf7bb6f3514bb834069 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.netconf.sal.connect.api;
 import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMNotification;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 public interface RemoteDeviceHandler<PREF> extends AutoCloseable {
@@ -39,6 +40,10 @@ public interface RemoteDeviceHandler<PREF> extends AutoCloseable {
         // DO NOTHING
     }
 
+    default void onDeviceReconnected(final PREF netconfSessionPreferences, final NetconfNode node) {
+        // DO NOTHING
+    }
+
     void onDeviceDisconnected();
 
     void onDeviceFailed(Throwable throwable);
index d7cddeb365c313c68b6afe1851ed67135bfcacea..451fb057c467850637732598e7def7a56ee9d4f5 100644 (file)
@@ -20,6 +20,7 @@ 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;
@@ -29,6 +30,7 @@ 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;
@@ -54,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;
@@ -95,6 +99,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;
@@ -120,16 +127,21 @@ public class NetconfDevice
                          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();
@@ -546,10 +558,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(
index 50a1e9ade98a7ae9a7c74861f25fd636692b3fff..9d262568e95853a71f391316ed8547e93ea7186b 100644 (file)
@@ -10,10 +10,13 @@ package org.opendaylight.netconf.sal.connect.netconf;
 
 import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import io.netty.util.concurrent.EventExecutor;
 import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory;
 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+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;
 
 public class NetconfDeviceBuilder {
 
@@ -23,6 +26,9 @@ public class NetconfDeviceBuilder {
     private RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
     private ListeningExecutorService globalProcessingExecutor;
     private DeviceActionFactory deviceActionFactory;
+    private NetconfNode node;
+    private EventExecutor eventExecutor;
+    private NetconfNodeAugmentedOptional nodeOptional;
 
     public NetconfDeviceBuilder() {
     }
@@ -57,10 +63,26 @@ public class NetconfDeviceBuilder {
         return this;
     }
 
+    public NetconfDeviceBuilder setNode(final NetconfNode node) {
+        this.node = node;
+        return this;
+    }
+
+    public NetconfDeviceBuilder setEventExecutor(final EventExecutor eventExecutor) {
+        this.eventExecutor = eventExecutor;
+        return this;
+    }
+
+    public NetconfDeviceBuilder setNodeOptional(final NetconfNodeAugmentedOptional nodeOptional) {
+        this.nodeOptional = nodeOptional;
+        return this;
+    }
+
     public NetconfDevice build() {
         validation();
         return new NetconfDevice(this.schemaResourcesDTO, this.id, this.salFacade, this.globalProcessingExecutor,
-                this.reconnectOnSchemasChange, this.deviceActionFactory);
+                this.reconnectOnSchemasChange, this.deviceActionFactory, this.node, this.eventExecutor,
+                this.nodeOptional);
     }
 
     private void validation() {
index 74378ce938a5d341cadce34b39f617c8dd9e27c6..aaf7db911f4ca16e011395cc58609892b8e80ef6 100644 (file)
@@ -12,6 +12,7 @@ import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import java.util.List;
 import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
@@ -21,6 +22,8 @@ import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+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.NetconfNodeConnectionStatus.ConnectionStatus;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -66,6 +69,13 @@ public final class NetconfDeviceSalFacade implements AutoCloseable, RemoteDevice
                 .updateDeviceData(true, netconfSessionPreferences.getNetconfDeviceCapabilities());
     }
 
+    @Override
+    public synchronized void onDeviceReconnected(final NetconfSessionPreferences netconfSessionPreferences,
+            final NetconfNode node) {
+        this.salProvider.getTopologyDatastoreAdapter().updateDeviceData(ConnectionStatus.Connecting,
+                netconfSessionPreferences.getNetconfDeviceCapabilities(), LogicalDatastoreType.CONFIGURATION, node);
+    }
+
     @Override
     public synchronized void onDeviceDisconnected() {
         salProvider.getTopologyDatastoreAdapter().updateDeviceData(false, new NetconfDeviceCapabilities());
index 1e65ec83df5683095cc10aa84050f68ec5754031..202517fb14d5c98b9950f016dacac6d6190ab494 100644 (file)
@@ -96,20 +96,30 @@ public class NetconfDeviceTopologyAdapter implements AutoCloseable {
         commitTransaction(writeTx, "init");
     }
 
-    public void updateDeviceData(final boolean up, final NetconfDeviceCapabilities capabilities) {
-        final NetconfNode data = buildDataForNetconfNode(up, capabilities);
+    public void updateDeviceData(final ConnectionStatus connectionStatus,
+            final NetconfDeviceCapabilities capabilities, final LogicalDatastoreType dsType, final NetconfNode node) {
+        NetconfNode data;
+        if (node != null && dsType == LogicalDatastoreType.CONFIGURATION) {
+            data = node;
+        } else {
+            data = buildDataForNetconfNode(connectionStatus, capabilities, dsType, node);
+        }
 
         final WriteTransaction writeTx = txChain.newWriteOnlyTransaction();
         LOG.trace("{}: Update device state transaction {} merging operational data started.",
                 id, writeTx.getIdentifier());
-        writeTx.put(LogicalDatastoreType.OPERATIONAL, id.getTopologyBindingPath().augmentation(NetconfNode.class),
-            data, true);
+        writeTx.put(dsType, id.getTopologyBindingPath().augmentation(NetconfNode.class), data, true);
         LOG.trace("{}: Update device state transaction {} merging operational data ended.",
                 id, writeTx.getIdentifier());
 
         commitTransaction(writeTx, "update");
     }
 
+    public void updateDeviceData(final boolean up, final NetconfDeviceCapabilities capabilities) {
+        updateDeviceData(up ? ConnectionStatus.Connected : ConnectionStatus.Connecting, capabilities,
+                LogicalDatastoreType.OPERATIONAL, null);
+    }
+
     public void updateClusteredDeviceData(final boolean up, final String masterAddress,
                                           final NetconfDeviceCapabilities capabilities) {
         final NetconfNode data = buildDataForNetconfClusteredNode(up, masterAddress, capabilities);
@@ -146,7 +156,8 @@ public class NetconfDeviceTopologyAdapter implements AutoCloseable {
         commitTransaction(writeTx, "update-failed-device");
     }
 
-    private NetconfNode buildDataForNetconfNode(final boolean up, final NetconfDeviceCapabilities capabilities) {
+    private NetconfNode buildDataForNetconfNode(final ConnectionStatus connectionStatus,
+            final NetconfDeviceCapabilities capabilities, final LogicalDatastoreType dsType, final NetconfNode node) {
         List<AvailableCapability> capabilityList = new ArrayList<>();
         capabilityList.addAll(capabilities.getNonModuleBasedCapabilities());
         capabilityList.addAll(capabilities.getResolvedCapabilities());
@@ -154,14 +165,13 @@ public class NetconfDeviceTopologyAdapter implements AutoCloseable {
         final AvailableCapabilitiesBuilder avCapabalitiesBuilder = new AvailableCapabilitiesBuilder();
         avCapabalitiesBuilder.setAvailableCapability(capabilityList);
 
-        final NetconfNodeBuilder netconfNodeBuilder = new NetconfNodeBuilder()
-                .setHost(id.getHost())
-                .setPort(new PortNumber(id.getAddress().getPort()))
-                .setConnectionStatus(up ? ConnectionStatus.Connected : ConnectionStatus.Connecting)
-                .setAvailableCapabilities(avCapabalitiesBuilder.build())
-                .setUnavailableCapabilities(unavailableCapabilities(capabilities.getUnresolvedCapabilites()));
-
-        return netconfNodeBuilder.build();
+        return new NetconfNodeBuilder()
+            .setHost(id.getHost())
+            .setPort(new PortNumber(id.getAddress().getPort()))
+            .setConnectionStatus(connectionStatus)
+            .setAvailableCapabilities(avCapabalitiesBuilder.build())
+            .setUnavailableCapabilities(unavailableCapabilities(capabilities.getUnresolvedCapabilites()))
+            .build();
     }
 
     private NetconfNode buildDataForNetconfClusteredNode(final boolean up, final String masterNodeAddress,
diff --git a/netconf/sal-netconf-connector/src/main/yang/netconf-node-optional.yang b/netconf/sal-netconf-connector/src/main/yang/netconf-node-optional.yang
new file mode 100644 (file)
index 0000000..8e9d632
--- /dev/null
@@ -0,0 +1,69 @@
+module netconf-node-optional {
+    namespace "urn:opendaylight:netconf-node-optional";
+    prefix "netnopt";
+
+    import network-topology { prefix nt; revision-date 2013-10-21; }
+    import yang-ext { prefix ext; revision-date "2013-07-09";}
+
+    revision "2019-06-14" {
+        description "Initial revision of Node Optional model";
+    }
+
+    grouping netconf-node-augmented-optional-fields {
+        container ignore-missing-schema-sources {
+            description "Allows mount point to reconnect on the 'missing schema sources' error.
+                WARNING - enabling the reconnection on the 'missing schema sources' error can lead
+                to unexpected errors at runtime.";
+            leaf allowed {
+                type boolean;
+                default false;
+                description "Allows reconnection of the mount point. Default false.";
+            }
+            leaf reconnect-time {
+                type uint32;
+                default 5000;
+                description "Time for reconnection - in units milliseconds. Default 5000 ms.";
+            }
+        }
+    }
+
+    container netconf-node-fields-optional {
+        description "Allows to create node's optional value with the path mapping according to
+            the network-topology -> topology -> node";
+        list topology {
+            key topology-id;
+            leaf topology-id {
+                type nt:topology-id;
+                description "The name of node's topology";
+            }
+            list node {
+                key node-id;
+                leaf node-id {
+                    type nt:node-id;
+                    description "The identifier of a node in the topology";
+                }
+                // Containers allow to create specific data-change-listener directly on a node's optional value.
+                // In the future, it'll be easy to extend the node by optional node fields in this way. Do not create
+                // direct leafs here, please.
+                container datastore-lock {
+                    description "Allows to ignore lock/unlock node's datastare.";
+                    leaf datastore-lock-allowed {
+                        type boolean;
+                        default true;
+                        description "The operation allows the client to lock the entire configuration datastore
+                            system of a device.
+                            WARNING - With blocking the lock/unlock operations, the user is coming to operate
+                            in a manner which is not supported. It must not exist any concurrent access to
+                            the data store - it may interfere with data consistency.";
+                    }
+                }
+            }
+        }
+    }
+
+    augment "/nt:network-topology/nt:topology/nt:node/" {
+        when "../../nt:topology-types/topology-netconf";
+        ext:augment-identifier "netconf-node-augmented-optional";
+        uses netconf-node-augmented-optional-fields;
+    }
+}