NETCONF southbound requires notifications.yang model to be present on the device 09/89909/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 20:28:14 +0000 (13:28 -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 ef28d7252dc7d317c03120a542b21c2460683fc2..4cc09857fd2999266cf4f6cc47a377329bf54337 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;
@@ -434,6 +436,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..13ba4ff22dd4ac67a2b05f43b7e38b1a49eee3c2 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,133 @@ 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;