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;
protected ListenableFuture<NetconfDeviceCapabilities> setupConnection(final NodeId nodeId,
final Node configNode) {
final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
+ final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
requireNonNull(netconfNode.getHost());
requireNonNull(netconfNode.getPort());
- 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 =
}
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();
.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);
}
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 {
// DO NOTHING
}
+ default void onDeviceReconnected(final PREF netconfSessionPreferences, final NetconfNode node) {
+ // DO NOTHING
+ }
+
void onDeviceDisconnected();
void onDeviceFailed(Throwable throwable);
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.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.opendaylight.mdsal.dom.api.DOMNotification;
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;
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;
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();
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(
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 {
private RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
private ListeningExecutorService globalProcessingExecutor;
private DeviceActionFactory deviceActionFactory;
+ private NetconfNode node;
+ private EventExecutor eventExecutor;
+ private NetconfNodeAugmentedOptional nodeOptional;
public 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() {
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.netconf.node.fields.optional.topology.Node;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.netconf.node.fields.optional.topology.NodeKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.netconf.node.fields.optional.topology.node.DatastoreLock;
+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.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
.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());
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);
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());
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,
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 Locking model";
+ 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 {
}
}
}
+
+ 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;
+ }
}