From 33d6351332b51e2cfd454daf673c7663b03f096c Mon Sep 17 00:00:00 2001 From: Jakub Toth Date: Mon, 24 Jun 2019 05:17:16 -0400 Subject: [PATCH] Attempt netconf remount regardless of error-type 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 --- .../topology/AbstractNetconfTopology.java | 14 +++- .../sal/connect/api/RemoteDeviceHandler.java | 5 ++ .../sal/connect/netconf/NetconfDevice.java | 35 ++++++++-- .../connect/netconf/NetconfDeviceBuilder.java | 24 ++++++- .../netconf/sal/NetconfDeviceSalFacade.java | 10 +++ .../sal/NetconfDeviceTopologyAdapter.java | 36 ++++++---- .../src/main/yang/netconf-node-optional.yang | 69 +++++++++++++++++++ 7 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 netconf/sal-netconf-connector/src/main/yang/netconf-node-optional.yang diff --git a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java index a625dd3a49..cb96ffb405 100644 --- a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java +++ b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java @@ -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 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); } diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java index f762b889a9..9ab98f6428 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/api/RemoteDeviceHandler.java @@ -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 extends AutoCloseable { @@ -39,6 +40,10 @@ public interface RemoteDeviceHandler extends AutoCloseable { // DO NOTHING } + default void onDeviceReconnected(final PREF netconfSessionPreferences, final NetconfNode node) { + // DO NOTHING + } + void onDeviceDisconnected(); void onDeviceFailed(Throwable throwable); 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 d7cddeb365..451fb057c4 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 @@ -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 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 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 handleMissingSchemaSourceException( diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java index 50a1e9ade9..9d262568e9 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceBuilder.java @@ -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 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() { diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java index 74378ce938..aaf7db911f 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceSalFacade.java @@ -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()); diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceTopologyAdapter.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceTopologyAdapter.java index 1e65ec83df..202517fb14 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceTopologyAdapter.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceTopologyAdapter.java @@ -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 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 index 0000000000..8e9d632a54 --- /dev/null +++ b/netconf/sal-netconf-connector/src/main/yang/netconf-node-optional.yang @@ -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; + } +} -- 2.36.6