Merge "NETCONF-557: Add support for URL capability"
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / NetconfDevice.java
index b78011460e4e81f4544c79863184e7f7089ea429..633db8581b43c05d9d121d9bb3f74c69f234cf83 100644 (file)
@@ -26,14 +26,17 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 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.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;
@@ -60,7 +63,6 @@ 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.SchemaSourceException;
 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;
@@ -68,7 +70,6 @@ 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.util.ASTSchemaSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -81,17 +82,20 @@ 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;
-    private final NetconfDeviceSchemasResolver stateSchemasResolver;
-    private final NotificationHandler notificationHandler;
+
     protected final List<SchemaSourceRegistration<? extends SchemaSourceRepresentation>> sourceRegistrations =
             new ArrayList<>();
+
+    private final RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
+    private final ListeningExecutorService processingExecutor;
+    private final DeviceActionFactory deviceActionFactory;
+    private final NetconfDeviceSchemasResolver stateSchemasResolver;
+    private final NotificationHandler notificationHandler;
+    private final boolean reconnectOnSchemasChange;
+
     @GuardedBy("this")
     private boolean connected = false;
 
@@ -115,8 +119,16 @@ public class NetconfDevice
     public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id,
                          final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
                          final ExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange) {
+        this(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange, null);
+    }
+
+    public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id,
+            final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
+            final ExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange,
+            final DeviceActionFactory deviceActionFactory) {
         this.id = id;
         this.reconnectOnSchemasChange = reconnectOnSchemasChange;
+        this.deviceActionFactory = deviceActionFactory;
         this.schemaRegistry = schemaResourcesDTO.getSchemaRegistry();
         this.schemaRepository = schemaResourcesDTO.getSchemaRepository();
         this.schemaContextFactory = schemaResourcesDTO.getSchemaContextFactory();
@@ -149,7 +161,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,12 +172,12 @@ 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);
             }
         };
 
-        Futures.addCallback(sourceResolverFuture, resolvedSourceCallback);
+        Futures.addCallback(sourceResolverFuture, resolvedSourceCallback, MoreExecutors.directExecutor());
     }
 
     private void registerToBaseNetconfStream(final NetconfDeviceRpc deviceRpc,
@@ -206,7 +218,7 @@ public class NetconfDevice
                 LOG.warn("Unable to subscribe to base notification stream. Schemas will not be reloaded on the fly",
                         throwable);
             }
-        });
+        }, MoreExecutors.directExecutor());
     }
 
     private boolean shouldListenOnSchemaChange(final NetconfSessionPreferences remoteSessionCapabilities) {
@@ -215,19 +227,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 +265,7 @@ public class NetconfDevice
         updateTransformer(null);
     }
 
-    private void updateTransformer(final MessageTransformer<NetconfMessage> transformer) {
+    private synchronized void updateTransformer(final MessageTransformer<NetconfMessage> transformer) {
         messageTransformer = transformer;
     }
 
@@ -353,7 +368,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,
@@ -431,8 +446,7 @@ public class NetconfDevice
         }
 
         private static SourceIdentifier toSourceId(final QName input) {
-            return RevisionSourceIdentifier.create(input.getLocalName(),
-                Optional.fromNullable(input.getFormattedRevision()));
+            return RevisionSourceIdentifier.create(input.getLocalName(), input.getRevision());
         }
     }
 
@@ -467,29 +481,29 @@ public class NetconfDevice
         }
 
         private Collection<SourceIdentifier> filterMissingSources(final Collection<SourceIdentifier> requiredSources) {
-
             return requiredSources.parallelStream().filter(sourceIdentifier -> {
-                boolean remove = false;
                 try {
-                    schemaRepository.getSchemaSource(sourceIdentifier, ASTSchemaSource.class).checkedGet();
-                } catch (SchemaSourceException e) {
-                    remove = true;
+                    schemaRepository.getSchemaSource(sourceIdentifier, YangTextSchemaSource.class).get();
+                    return false;
+                } catch (InterruptedException | ExecutionException e) {
+                    return true;
                 }
-                return remove;
             }).collect(Collectors.toList());
         }
 
         /**
          * 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) {
             while (!requiredSources.isEmpty()) {
                 LOG.trace("{}: Trying to build schema context from {}", id, requiredSources);
                 try {
-                    final CheckedFuture<SchemaContext, SchemaResolutionException> schemaBuilderFuture =
-                            schemaContextFactory.createSchemaContext(requiredSources);
-                    final SchemaContext result = schemaBuilderFuture.checkedGet();
+                    final ListenableFuture<SchemaContext> schemaBuilderFuture = schemaContextFactory
+                            .createSchemaContext(requiredSources);
+                    final SchemaContext result = schemaBuilderFuture.get();
                     LOG.debug("{}: Schema context built successfully from {}", id, requiredSources);
                     final Collection<QName> filteredQNames = Sets.difference(deviceSources.getRequiredSourcesQName(),
                             capabilities.getUnresolvedCapabilites().keySet());
@@ -504,18 +518,27 @@ 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 SchemaResolutionException e) {
+                } catch (final ExecutionException e) {
                     // schemaBuilderFuture.checkedGet() throws only SchemaResolutionException
                     // that might be wrapping a MissingSchemaSourceException so we need to look
                     // at the cause of the exception to make sure we don't misinterpret it.
-                    if (e.getCause() instanceof MissingSchemaSourceException) {
+                    final Throwable cause = e.getCause();
+
+                    if (cause instanceof MissingSchemaSourceException) {
                         requiredSources = handleMissingSchemaSourceException(
-                                requiredSources, (MissingSchemaSourceException) e.getCause());
+                                requiredSources, (MissingSchemaSourceException) cause);
                         continue;
                     }
-                    requiredSources = handleSchemaResolutionException(requiredSources, e);
+                    if (cause instanceof SchemaResolutionException) {
+                        requiredSources = handleSchemaResolutionException(requiredSources,
+                            (SchemaResolutionException) cause);
+                    } else {
+                        handleSalInitializationFailure(e, listener);
+                        return;
+                    }
                 } catch (final Exception e) {
                     // unknown error, fail
                     handleSalInitializationFailure(e, listener);
@@ -532,12 +555,12 @@ public class NetconfDevice
                 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);
@@ -554,21 +577,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();
         }
 
@@ -602,12 +626,7 @@ public class NetconfDevice
                     continue;
                 }
 
-                final String rev = getNullableRev(identifier);
-                if (rev == null) {
-                    if (qname.getRevision() == null) {
-                        return qname;
-                    }
-                } else if (qname.getFormattedRevision().equals(rev)) {
+                if (identifier.getRevision().equals(qname.getRevision())) {
                     return qname;
                 }
             }
@@ -617,10 +636,5 @@ public class NetconfDevice
             // this capability will be removed from required sources and not reported as unresolved-capability
             return null;
         }
-
-        private String getNullableRev(final SourceIdentifier identifier) {
-            final String rev = identifier.getRevision();
-            return rev == null || SourceIdentifier.NOT_PRESENT_FORMATTED_REVISION.equals(rev) ? null : rev;
-        }
     }
 }