Bug-6346: Allow over-ride of non-module capabilities
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / NetconfDevice.java
index 3edbaf1018099b140f91298c615225814b31098e..d2f73ae468b37d52f7e706ce59fc0a0a94ed18b8 100644 (file)
@@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -49,6 +50,7 @@ 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.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;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -76,13 +78,9 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
 
     private static final Logger LOG = LoggerFactory.getLogger(NetconfDevice.class);
 
-    public static final Function<QName, SourceIdentifier> QNAME_TO_SOURCE_ID_FUNCTION = new Function<QName, SourceIdentifier>() {
-        @Override
-        public SourceIdentifier apply(final QName input) {
-            return RevisionSourceIdentifier
-                    .create(input.getLocalName(), Optional.fromNullable(input.getFormattedRevision()));
-        }
-    };
+    public static final Function<QName, SourceIdentifier> QNAME_TO_SOURCE_ID_FUNCTION =
+            input -> RevisionSourceIdentifier.create(input.getLocalName(),
+                Optional.fromNullable(input.getFormattedRevision()));
 
     protected final RemoteDeviceId id;
     private final boolean reconnectOnSchemasChange;
@@ -110,7 +108,7 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
         return new NetconfDeviceRpc(baseSchema.getSchemaContext(), listener, new NetconfMessageTransformer(baseSchema.getSchemaContext(), false, baseSchema));
     }
 
-    protected NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
+    public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
                          final ExecutorService globalProcessingExecutor, final boolean reconnectOnSchemasChange) {
         this.id = id;
         this.reconnectOnSchemasChange = reconnectOnSchemasChange;
@@ -165,8 +163,9 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
     private void registerToBaseNetconfStream(final NetconfDeviceRpc deviceRpc, final NetconfDeviceCommunicator listener) {
        // 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(NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME), NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_CONTENT);
+        final CheckedFuture<DOMRpcResult, DOMRpcException> rpcResultListenableFuture = deviceRpc.invokeRpc(
+            NetconfMessageTransformUtil.toPath(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME),
+            NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_CONTENT);
 
         final NotificationHandler.NotificationFilter filter = new NotificationHandler.NotificationFilter() {
             @Override
@@ -202,7 +201,7 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
         return remoteSessionCapabilities.isNotificationsSupported() && reconnectOnSchemasChange;
     }
 
-    protected void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionPreferences remoteSessionCapabilities, final DOMRpcService deviceRpc) {
+    void handleSalInitializationSuccess(final SchemaContext result, final NetconfSessionPreferences remoteSessionCapabilities, final DOMRpcService deviceRpc) {
         final BaseSchema baseSchema =
                 remoteSessionCapabilities.isNotificationsSupported() ?
                 BaseSchema.BASE_NETCONF_CTX_WITH_NOTIFICATIONS :
@@ -217,7 +216,7 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
         LOG.info("{}: Netconf connector initialized successfully", id);
     }
 
-    protected void handleSalInitializationFailure(final Throwable t, final RemoteDeviceCommunicator<NetconfMessage> listener) {
+    void handleSalInitializationFailure(final Throwable t, final RemoteDeviceCommunicator<NetconfMessage> listener) {
         LOG.error("{}: Initialization in sal failed, disconnecting from device", id, t);
         listener.close();
         onRemoteSessionDown();
@@ -451,8 +450,14 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
                     final SchemaContext result = schemaBuilderFuture.checkedGet();
                     LOG.debug("{}: Schema context built successfully from {}", id, requiredSources);
                     final Collection<QName> filteredQNames = Sets.difference(deviceSources.getRequiredSourcesQName(), capabilities.getUnresolvedCapabilites().keySet());
-                    capabilities.addCapabilities(filteredQNames);
-                    capabilities.addNonModuleBasedCapabilities(remoteSessionCapabilities.getNonModuleCaps());
+                    capabilities.addCapabilities(filteredQNames.stream().map(entry -> new AvailableCapabilityBuilder()
+                            .setCapability(entry.toString()).setCapabilityOrigin(remoteSessionCapabilities.getModuleBasedCapsOrigin().get(entry)).build())
+                            .collect(Collectors.toList()));
+
+                    capabilities.addNonModuleBasedCapabilities(remoteSessionCapabilities.getNonModuleCaps().stream().map(entry -> new AvailableCapabilityBuilder()
+                            .setCapability(entry).setCapabilityOrigin(remoteSessionCapabilities.getNonModuleBasedCapsOrigin().get(entry)).build())
+                            .collect(Collectors.toList()));
+
                     handleSalInitializationSuccess(result, remoteSessionCapabilities, getDeviceSpecificRpc(result));
                     return;
                 } catch (final Throwable t) {
@@ -489,11 +494,23 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
             if (!qNameOfMissingSource.isEmpty()) {
                 capabilities.addUnresolvedCapabilities(qNameOfMissingSource, UnavailableCapability.FailureReason.MissingSource);
             }
-            return stripMissingSource(requiredSources, missingSource);
+            return stripUnavailableSource(requiredSources, missingSource);
         }
 
         private Collection<SourceIdentifier> handleSchemaResolutionException(final Collection<SourceIdentifier> requiredSources, final SchemaResolutionException resolutionException) {
             // In case resolution error, try only with resolved sources
+            // There are two options why schema resolution exception occurred : unsatisfied imports or flawed model
+            // FIXME Do we really have assurance that these two cases cannot happen at once?
+            if (resolutionException.getFailedSource() != null) {
+                // 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);
+                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);
+                return stripUnavailableSource(requiredSources, resolutionException.getFailedSource());
+            }
+            // unsatisfied imports
             final Set<SourceIdentifier> unresolvedSources = resolutionException.getUnsatisfiedImports().keySet();
             capabilities.addUnresolvedCapabilities(getQNameFromSourceIdentifiers(unresolvedSources), UnavailableCapability.FailureReason.UnableToResolve);
             LOG.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only", id, resolutionException.getUnsatisfiedImports());
@@ -505,7 +522,7 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
             return new NetconfDeviceRpc(result, listener, new NetconfMessageTransformer(result, true));
         }
 
-        private Collection<SourceIdentifier> stripMissingSource(final Collection<SourceIdentifier> requiredSources, final SourceIdentifier sIdToRemove) {
+        private Collection<SourceIdentifier> stripUnavailableSource(final Collection<SourceIdentifier> requiredSources, final SourceIdentifier sIdToRemove) {
             final LinkedList<SourceIdentifier> sourceIdentifiers = Lists.newLinkedList(requiredSources);
             final boolean removed = sourceIdentifiers.remove(sIdToRemove);
             Preconditions.checkState(removed, "{}: Trying to remove {} from {} failed", id, sIdToRemove, requiredSources);
@@ -513,12 +530,7 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
         }
 
         private Collection<QName> getQNameFromSourceIdentifiers(final Collection<SourceIdentifier> identifiers) {
-            final Collection<QName> qNames = Collections2.transform(identifiers, new Function<SourceIdentifier, QName>() {
-                @Override
-                public QName apply(final SourceIdentifier sourceIdentifier) {
-                    return getQNameFromSourceIdentifier(sourceIdentifier);
-                }
-            });
+            final Collection<QName> qNames = Collections2.transform(identifiers, this::getQNameFromSourceIdentifier);
 
             if (qNames.isEmpty()) {
                 LOG.debug("{}: Unable to map any source identifiers to a capability reported by device : {}", id, identifiers);
@@ -529,16 +541,16 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
         private QName getQNameFromSourceIdentifier(final SourceIdentifier identifier) {
             // Required sources are all required and provided merged in DeviceSourcesResolver
             for (final QName qname : deviceSources.getRequiredSourcesQName()) {
-                if(qname.getLocalName().equals(identifier.getName()) == false) {
+                if (!qname.getLocalName().equals(identifier.getName())) {
                     continue;
                 }
 
-                if(identifier.getRevision().equals(SourceIdentifier.NOT_PRESENT_FORMATTED_REVISION) &&
-                        qname.getRevision() == null) {
-                    return qname;
-                }
-
-                if (qname.getFormattedRevision().equals(identifier.getRevision())) {
+                final String rev = getNullableRev(identifier);
+                if (rev == null) {
+                    if (qname.getRevision() == null) {
+                        return qname;
+                    }
+                } else if (qname.getFormattedRevision().equals(rev)) {
                     return qname;
                 }
             }
@@ -546,5 +558,10 @@ public class NetconfDevice implements RemoteDevice<NetconfSessionPreferences, Ne
             // return null since we cannot find the QName, 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;
+        }
     }
 }