NETCONF southbound requires notifications.yang model to be present on the device 08/89908/1
authorvladyslav.marchenko <Vladyslav.Marchenko@pantheon.tech>
Mon, 23 Mar 2020 15:05:23 +0000 (17:05 +0200)
committerJamo Luhrsen <jluhrsen@gmail.com>
Thu, 21 May 2020 18:12:26 +0000 (11:12 -0700)
There is no standardized YANG model for NETCONF notifications simply
because RFC5277 predates YANG and has not been updated. The presence
of the notification capability is enough for notifications to work –
the SchemaContext assembly code supplies the model internally based
on the capability.

JIRA: NETCONF-338
Signed-off-by: vladyslav.marchenko <Vladyslav.Marchenko@pantheon.tech>
Signed-off-by: Jamo Luhrsen <jluhrsen@gmail.com>
Change-Id: Iaec5eab02f8d43f752d90a3fd0ecbc0a57e9e75f

netconf/netconf-impl/src/main/java/org/opendaylight/netconf/impl/NetconfServerSessionNegotiatorFactory.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDevice.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/NetconfDeviceTest.java

index 619f97a6d4c3c5dd3942a03c4eb96a5199c3381f..0aefb841eb40c00b63f65a7cf384debc2735b305 100644 (file)
@@ -35,7 +35,8 @@ public class NetconfServerSessionNegotiatorFactory
     public static final Set<String> DEFAULT_BASE_CAPABILITIES = ImmutableSet.of(
             XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0,
             XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1,
-            XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_EXI_1_0
+            XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_EXI_1_0,
+            XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_NOTIFICATION_1_0
     );
 
     private final Timer timer;
index 917638add44d485fa5c7980a9166a8c3b9e5ddb4..0e83ac76182f4ab982186f8cc7410e7901c41a65 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.util.concurrent.SettableFuture;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import io.netty.util.concurrent.EventExecutor;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -36,6 +37,7 @@ import org.checkerframework.checker.lock.qual.GuardedBy;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.netconf.api.NetconfMessage;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
 import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory;
 import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
 import org.opendaylight.netconf.sal.connect.api.NetconfDeviceSchemasResolver;
@@ -441,6 +443,19 @@ public class NetconfDevice
             this.remoteSessionCapabilities = remoteSessionCapabilities;
             this.capabilities = remoteSessionCapabilities.getNetconfDeviceCapabilities();
 
+            // If device supports notifications and does not contain necessary modules, add them automatically
+            if (remoteSessionCapabilities.containsNonModuleCapability(
+                    XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_NOTIFICATION_1_0)) {
+                deviceSources.getRequiredSourcesQName().addAll(
+                        Arrays.asList(
+                                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
+                                        .$YangModuleInfoImpl.getInstance().getName(),
+                                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
+                                        .$YangModuleInfoImpl.getInstance().getName()
+                        )
+                );
+            }
+
             requiredSources = deviceSources.getRequiredSources();
             final Collection<SourceIdentifier> missingSources = filterMissingSources(requiredSources);
 
index 394797673d3cf176ad7d347522c7bac9f9ac1a0d..7de34bffaab8dc2bcd034cfbd068b99abeb3cf2a 100644 (file)
@@ -8,6 +8,8 @@
 package org.opendaylight.netconf.sal.connect.netconf;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyCollectionOf;
 import static org.mockito.ArgumentMatchers.eq;
@@ -32,10 +34,13 @@ import com.google.common.util.concurrent.SettableFuture;
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Executors;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -66,6 +71,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
@@ -411,8 +417,132 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
                                 QName.create(entry.getCapability())).getName(), entry.getCapabilityOrigin().getName()));
     }
 
-    private static SchemaContextFactory getSchemaFactory() throws Exception {
-        final SchemaContextFactory schemaFactory = mockClass(SchemaContextFactory.class);
+    @Test
+    public void testNetconfDeviceNotificationsModelNotPresentWithCapability() throws Exception {
+        final RemoteDeviceHandler<NetconfSessionPreferences> facade = getFacade();
+        final NetconfDeviceCommunicator listener = getListener();
+        final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
+
+        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(
+                getSchemaRegistry(), getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
+        final NetconfDevice device = new NetconfDeviceBuilder()
+                .setSchemaResourcesDTO(schemaResourcesDTO)
+                .setGlobalProcessingExecutor(getExecutor())
+                .setId(getId())
+                .setSalFacade(facade)
+                .build();
+        final NetconfDevice netconfSpy = Mockito.spy(device);
+
+        final NetconfSessionPreferences sessionCaps = getSessionCaps(false,
+                Lists.newArrayList(XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_NOTIFICATION_1_0));
+
+        netconfSpy.onRemoteSessionUp(sessionCaps, listener);
+
+        final ArgumentCaptor<NetconfSessionPreferences> argument =
+                ArgumentCaptor.forClass(NetconfSessionPreferences.class);
+        verify(facade, timeout(5000)).onDeviceConnected(any(MountPointContext.class), argument.capture(),
+                any(DOMRpcService.class), isNull());
+
+        List<String> notificationModulesName = Arrays.asList(
+                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
+                        .$YangModuleInfoImpl.getInstance().getName().toString(),
+                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
+                        .$YangModuleInfoImpl.getInstance().getName().toString());
+
+        final Set<AvailableCapability> resolvedCapabilities = argument.getValue().getNetconfDeviceCapabilities()
+                .getResolvedCapabilities();
+
+        assertEquals(2, resolvedCapabilities.size());
+        assertTrue(resolvedCapabilities.stream().anyMatch(entry -> notificationModulesName
+                .contains(entry.getCapability())));
+    }
+
+    @Test
+    public void testNetconfDeviceNotificationsCapabilityIsNotPresent() throws Exception {
+        final RemoteDeviceHandler<NetconfSessionPreferences> facade = getFacade();
+        final NetconfDeviceCommunicator listener = getListener();
+        final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
+
+        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(
+                getSchemaRegistry(), getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
+        final NetconfDevice device = new NetconfDeviceBuilder()
+                .setSchemaResourcesDTO(schemaResourcesDTO)
+                .setGlobalProcessingExecutor(getExecutor())
+                .setId(getId())
+                .setSalFacade(facade)
+                .build();
+        final NetconfDevice netconfSpy = Mockito.spy(device);
+
+        final NetconfSessionPreferences sessionCaps = getSessionCaps(false,
+                Lists.newArrayList(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION));
+
+        netconfSpy.onRemoteSessionUp(sessionCaps, listener);
+
+        final ArgumentCaptor<NetconfSessionPreferences> argument =
+                ArgumentCaptor.forClass(NetconfSessionPreferences.class);
+        verify(facade, timeout(5000)).onDeviceConnected(any(MountPointContext.class), argument.capture(),
+                any(DOMRpcService.class), isNull());
+        final NetconfDeviceCapabilities netconfDeviceCaps = argument.getValue().getNetconfDeviceCapabilities();
+
+        List<String> notificationModulesName = Arrays.asList(
+                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
+                        .$YangModuleInfoImpl.getInstance().getName().toString(),
+                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
+                        .$YangModuleInfoImpl.getInstance().getName().toString());
+
+        assertFalse(netconfDeviceCaps.getResolvedCapabilities().stream().anyMatch(entry -> notificationModulesName
+                .contains(entry.getCapability())));
+    }
+
+    @Test
+    public void testNetconfDeviceNotificationsModelIsPresent() throws Exception {
+        final RemoteDeviceHandler<NetconfSessionPreferences> facade = getFacade();
+        final NetconfDeviceCommunicator listener = getListener();
+        final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
+
+        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(
+                getSchemaRegistry(), getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
+        final NetconfDevice device = new NetconfDeviceBuilder()
+                .setSchemaResourcesDTO(schemaResourcesDTO)
+                .setGlobalProcessingExecutor(getExecutor())
+                .setId(getId())
+                .setSalFacade(facade)
+                .build();
+        final NetconfDevice netconfSpy = Mockito.spy(device);
+
+        final NetconfSessionPreferences sessionCaps = getSessionCaps(false, Collections.emptyList());
+
+        final Map<QName, AvailableCapability.CapabilityOrigin> moduleBasedCaps = new HashMap<>();
+        moduleBasedCaps.put(org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
+                        .$YangModuleInfoImpl.getInstance().getName(),
+                AvailableCapability.CapabilityOrigin.DeviceAdvertised);
+        moduleBasedCaps.put(org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
+                        .$YangModuleInfoImpl.getInstance().getName(),
+                AvailableCapability.CapabilityOrigin.DeviceAdvertised);
+
+
+        netconfSpy.onRemoteSessionUp(sessionCaps.replaceModuleCaps(moduleBasedCaps), listener);
+
+        final ArgumentCaptor<NetconfSessionPreferences> argument =
+                ArgumentCaptor.forClass(NetconfSessionPreferences.class);
+        verify(facade, timeout(5000)).onDeviceConnected(any(MountPointContext.class), argument.capture(),
+                any(DOMRpcService.class), isNull());
+        final Set<AvailableCapability> resolvedCapabilities = argument.getValue().getNetconfDeviceCapabilities()
+                .getResolvedCapabilities();
+
+        List<String> notificationModulesName = Arrays.asList(
+                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
+                        .$YangModuleInfoImpl.getInstance().getName().toString(),
+                org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
+                        .$YangModuleInfoImpl.getInstance().getName().toString());
+
+        assertEquals(2, resolvedCapabilities.size());
+        assertTrue(resolvedCapabilities.stream().anyMatch(entry -> notificationModulesName
+                .contains(entry.getCapability())));
+    }
+
+    private static EffectiveModelContextFactory getSchemaFactory() throws Exception {
+        final EffectiveModelContextFactory schemaFactory = mockClass(EffectiveModelContextFactory.class);
         doReturn(Futures.immediateFuture(SCHEMA_CONTEXT))
                 .when(schemaFactory).createSchemaContext(any(Collection.class));
         return schemaFactory;