Eliminate SchemaResourcesDTO 73/110273/16
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 21 Feb 2024 10:27:47 +0000 (11:27 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 25 Feb 2024 16:53:47 +0000 (17:53 +0100)
The process of setting up EffectiveModelContext is rather arcane, going
through multiple indirections, most of which are not really necessary.

This patch introduces DeviceNetconfSchemaProvider to replace the plain
SchemaResourcesDTO, centralizing the process into a single class.

As we now have a stricter lifecycle, we end up using invokeNetconf()
instead of invokeRpc(), making it clear we are only invoking base RPCs.

JIRA: NETCONF-840
Change-Id: I14d41f9f78332d2da9681de89a931dc01cd0829f
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
29 files changed:
apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/NetconfTopologyManager.java
apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/actors/NetconfNodeActor.java
apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/utils/NetconfTopologySetup.java
apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/MountPointEndToEndTest.java
apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeActorTest.java
apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeManagerTest.java
apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandler.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProvider.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDevice.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceBuilder.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceSchema.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemas.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/BaseNetconfSchemaProvider.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/DeviceNetconfSchema.java [new file with mode: 0644]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/DeviceNetconfSchemaProvider.java [new file with mode: 0644]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/SchemaResourceManager.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultDeviceNetconfSchemaProvider.java [new file with mode: 0644]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSchemaResourceManager.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DeviceSources.java [moved from plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/DeviceSources.java with 97% similarity]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetup.java [new file with mode: 0644]
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractBaseSchemasTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractTestModelTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProviderTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetupTest.java [new file with mode: 0644]

index bad929cca0d0d092a3900b4814eac74965afb832..66cb2b7f7372414cd7d4b1db44f605b1f0dae1f9 100644 (file)
@@ -326,7 +326,8 @@ public class NetconfTopologyManager implements DataTreeChangeListener<Node>, Aut
             .setSchemaAssembler(schemaAssembler)
             .setTopologyId(topologyId)
             .setNetconfClientFactory(clientFactory)
-            .setSchemaResourceDTO(resourceManager.getSchemaResources(netconfNode.getSchemaCacheDirectory(), deviceId))
+            .setDeviceSchemaProvider(resourceManager.getSchemaResources(netconfNode.getSchemaCacheDirectory(),
+                deviceId))
             .setIdleTimeout(writeTxIdleTimeout)
             .build();
     }
index 21bf46a8a4e2302b6f74444b1bcdb1e620fc2516..12b8350372e04764b880f7f89fe71d2d1b855410 100644 (file)
@@ -35,7 +35,7 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Actions;
@@ -81,7 +81,7 @@ public class NetconfNodeActor extends AbstractUntypedActor {
     private final Duration writeTxIdleTimeout;
     private final DOMMountPointService mountPointService;
 
-    private SchemaResourcesDTO schemaResources;
+    private DeviceNetconfSchemaProvider schemaProvider;
     private Timeout actorResponseWaitTime;
     private RemoteDeviceId id;
     private List<SourceIdentifier> sourceIdentifiers = null;
@@ -97,7 +97,7 @@ public class NetconfNodeActor extends AbstractUntypedActor {
     protected NetconfNodeActor(final NetconfTopologySetup setup, final RemoteDeviceId id,
             final Timeout actorResponseWaitTime, final DOMMountPointService mountPointService) {
         this.id = id;
-        schemaResources = setup.getSchemaResourcesDTO();
+        schemaProvider = setup.getDeviceSchemaProvider();
         this.actorResponseWaitTime = actorResponseWaitTime;
         writeTxIdleTimeout = setup.getIdleTimeout();
         this.mountPointService = mountPointService;
@@ -129,7 +129,7 @@ public class NetconfNodeActor extends AbstractUntypedActor {
             LOG.debug("{}: Master is ready.", id);
         } else if (message instanceof RefreshSetupMasterActorData masterActorData) {
             id = masterActorData.getRemoteDeviceId();
-            schemaResources = masterActorData.getNetconfTopologyDeviceSetup().getSchemaResourcesDTO();
+            schemaProvider = masterActorData.getNetconfTopologyDeviceSetup().getDeviceSchemaProvider();
             sender().tell(new MasterActorDataInitialized(), self());
         } else if (message instanceof AskForMasterMountPoint askForMasterMountPoint) { // master
             // only master contains reference to deviceDataBroker
@@ -177,7 +177,7 @@ public class NetconfNodeActor extends AbstractUntypedActor {
         } else if (message instanceof RefreshSlaveActor refreshSlave) { //slave
             actorResponseWaitTime = refreshSlave.getActorResponseWaitTime();
             id = refreshSlave.getId();
-            schemaResources = refreshSlave.getSetup().getSchemaResourcesDTO();
+            schemaProvider = refreshSlave.getSetup().getDeviceSchemaProvider();
         } else if (message instanceof NetconfDataTreeServiceRequest) {
             final var netconfActor = context().actorOf(NetconfDataTreeServiceActor.props(netconfService,
                 writeTxIdleTimeout));
@@ -204,8 +204,7 @@ public class NetconfNodeActor extends AbstractUntypedActor {
     }
 
     private void sendYangTextSchemaSourceProxy(final SourceIdentifier sourceIdentifier, final ActorRef sender) {
-        Futures.addCallback(
-            schemaResources.getSchemaRepository().getSchemaSource(sourceIdentifier, YangTextSource.class),
+        Futures.addCallback(schemaProvider.repository().getSchemaSource(sourceIdentifier, YangTextSource.class),
             new FutureCallback<>() {
                 @Override
                 public void onSuccess(final YangTextSource yangTextSchemaSource) {
@@ -308,14 +307,14 @@ public class NetconfNodeActor extends AbstractUntypedActor {
         final var remoteYangTextSourceProvider = new ProxyYangTextSourceProvider(masterReference, dispatcher,
             actorResponseWaitTime);
         final var remoteProvider = new RemoteSchemaProvider(remoteYangTextSourceProvider, dispatcher);
-        final var schemaRegistry = schemaResources.getSchemaRegistry();
+        final var schemaRegistry = schemaProvider.registry();
 
         registeredSchemas = sourceIdentifiers.stream()
             .map(sourceId -> schemaRegistry.registerSchemaSource(remoteProvider, PotentialSchemaSource.create(sourceId,
                 YangTextSource.class, PotentialSchemaSource.Costs.REMOTE_IO.getValue())))
             .collect(Collectors.toList());
 
-        return schemaResources.getSchemaRepository().createEffectiveModelContextFactory();
+        return schemaProvider.repository().createEffectiveModelContextFactory();
     }
 
     private void resolveSchemaContext(final EffectiveModelContextFactory schemaContextFactory,
index 08286fe4c9cd66a4c17bd1c011bad9060c4c84c9..7ec8e102069e96f9f17af0a54819eff1b4e6b3bd 100644 (file)
@@ -15,8 +15,8 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.binding.api.DataBroker;
 import org.opendaylight.mdsal.singleton.api.ClusterSingletonServiceProvider;
 import org.opendaylight.netconf.client.NetconfClientFactory;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice;
 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
 import org.opendaylight.netconf.common.NetconfTimer;
 import org.opendaylight.netconf.topology.spi.NetconfTopologySchemaAssembler;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
@@ -32,7 +32,7 @@ public final class NetconfTopologySetup {
     private final ActorSystem actorSystem;
     private final NetconfClientFactory netconfClientFactory;
     private final String topologyId;
-    private final NetconfDevice.SchemaResourcesDTO schemaResourceDTO;
+    private final DeviceNetconfSchemaProvider deviceSchemaProvider;
     private final Duration idleTimeout;
     private final BaseNetconfSchemaProvider baseSchemaProvider;
 
@@ -46,7 +46,7 @@ public final class NetconfTopologySetup {
         actorSystem = builder.getActorSystem();
         netconfClientFactory = builder.getNetconfClientFactory();
         topologyId = builder.getTopologyId();
-        schemaResourceDTO = builder.getSchemaResourceDTO();
+        deviceSchemaProvider = builder.getDeviceSchemaProvider();
         idleTimeout = builder.getIdleTimeout();
         baseSchemaProvider = builder.getBaseSchemaProvider();
     }
@@ -87,8 +87,8 @@ public final class NetconfTopologySetup {
         return netconfClientFactory;
     }
 
-    public NetconfDevice.SchemaResourcesDTO getSchemaResourcesDTO() {
-        return schemaResourceDTO;
+    public DeviceNetconfSchemaProvider getDeviceSchemaProvider() {
+        return deviceSchemaProvider;
     }
 
     public Duration getIdleTimeout() {
@@ -113,7 +113,7 @@ public final class NetconfTopologySetup {
         private ActorSystem actorSystem;
         private String topologyId;
         private NetconfClientFactory netconfClientFactory;
-        private NetconfDevice.SchemaResourcesDTO schemaResourceDTO;
+        private DeviceNetconfSchemaProvider deviceSchemaProvider;
         private Duration idleTimeout;
         private BaseNetconfSchemaProvider baseSchemaProvider;
 
@@ -217,14 +217,13 @@ public final class NetconfTopologySetup {
             return this;
         }
 
-        public Builder setSchemaResourceDTO(
-                final NetconfDevice.SchemaResourcesDTO schemaResourceDTO) {
-            this.schemaResourceDTO = schemaResourceDTO;
+        public Builder setDeviceSchemaProvider(final DeviceNetconfSchemaProvider deviceSchemaProvider) {
+            this.deviceSchemaProvider = deviceSchemaProvider;
             return this;
         }
 
-        NetconfDevice.SchemaResourcesDTO getSchemaResourceDTO() {
-            return schemaResourceDTO;
+        DeviceNetconfSchemaProvider getDeviceSchemaProvider() {
+            return deviceSchemaProvider;
         }
 
         public Builder setIdleTimeout(final Duration idleTimeout) {
index cbc5839beb7742eb51333349b51b1bf83120f5ec..b3989112f5ba458b757696ecebf3a2a989fed0f1 100644 (file)
@@ -296,7 +296,7 @@ public class MountPointEndToEndTest extends AbstractBaseSchemasTest {
             new SimpleDOMEntityOwnershipService());
 
         final var resources =  resourceManager.getSchemaResources(TEST_DEFAULT_SUBDIR, "test");
-        resources.getSchemaRegistry().registerSchemaSource(
+        resources.registry().registerSchemaSource(
             id -> Futures.immediateFuture(new DelegatedYangTextSource(id, topModuleInfo.getYangTextCharSource())),
             PotentialSchemaSource.create(new SourceIdentifier(TOP_MODULE_NAME,
                     topModuleInfo.getName().getRevision().map(Revision::toString).orElse(null)),
index a2f3f12cb8d8789379c7743ea7bbd2b4ce03c1e8..19fd22da6f9083223c97fe0f8d6d39f8f2a61074 100644 (file)
@@ -81,7 +81,7 @@ import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
 import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Actions;
@@ -169,7 +169,7 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
     @Mock
     private EffectiveModelContext mockSchemaContext;
     @Mock
-    private SchemaResourcesDTO schemaResourceDTO;
+    private DeviceNetconfSchemaProvider deviceSchemaProvider;
 
     @Before
     public void setup() {
@@ -182,7 +182,7 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
         final NetconfTopologySetup setup = NetconfTopologySetup.builder()
             .setActorSystem(system)
             .setIdleTimeout(Duration.ofSeconds(1))
-            .setSchemaResourceDTO(schemaResourceDTO)
+            .setDeviceSchemaProvider(deviceSchemaProvider)
             .setBaseSchemaProvider(BASE_SCHEMAS)
             .build();
 
@@ -223,7 +223,7 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
 
         final NetconfTopologySetup newSetup = NetconfTopologySetup.builder()
             .setBaseSchemaProvider(BASE_SCHEMAS)
-            .setSchemaResourceDTO(schemaResourceDTO)
+            .setDeviceSchemaProvider(deviceSchemaProvider)
             .setActorSystem(system)
             .build();
 
@@ -325,14 +325,14 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
         verify(newMockSchemaSourceReg).close();
     }
 
-    @SuppressWarnings("unchecked")
+//    @SuppressWarnings("unchecked")
     @Test
     public void testRegisterMountPointWithSchemaFailures() throws Exception {
-        SchemaResourcesDTO schemaResourceDTO2 = mock(SchemaResourcesDTO.class);
-        doReturn(mockRegistry).when(schemaResourceDTO2).getSchemaRegistry();
-        doReturn(mockSchemaRepository).when(schemaResourceDTO2).getSchemaRepository();
+        var deviceSchemaProvider2 = mock(DeviceNetconfSchemaProvider.class);
+        doReturn(mockRegistry).when(deviceSchemaProvider2).registry();
+        doReturn(mockSchemaRepository).when(deviceSchemaProvider2).repository();
         final NetconfTopologySetup setup = NetconfTopologySetup.builder()
-                .setSchemaResourceDTO(schemaResourceDTO2)
+                .setDeviceSchemaProvider(deviceSchemaProvider2)
                 .setBaseSchemaProvider(BASE_SCHEMAS)
                 .setActorSystem(system)
                 .build();
@@ -413,11 +413,11 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
     public void testMissingSchemaSourceOnMissingProvider() throws Exception {
         final var repository = new SharedSchemaRepository("test");
 
-        final var schemaResourceDTO2 = mock(SchemaResourcesDTO.class);
-        doReturn(repository).when(schemaResourceDTO2).getSchemaRepository();
+        final var deviceSchemaProvider2 = mock(DeviceNetconfSchemaProvider.class);
+        doReturn(repository).when(deviceSchemaProvider2).repository();
         final var setup = NetconfTopologySetup.builder()
             .setActorSystem(system)
-            .setSchemaResourceDTO(schemaResourceDTO2)
+            .setDeviceSchemaProvider(deviceSchemaProvider2)
             .setIdleTimeout(Duration.ofSeconds(1))
             .setBaseSchemaProvider(BASE_SCHEMAS)
             .build();
@@ -436,7 +436,7 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
 
     @Test
     public void testYangTextSchemaSourceRequest() throws Exception {
-        doReturn(masterSchemaRepository).when(schemaResourceDTO).getSchemaRepository();
+        doReturn(masterSchemaRepository).when(deviceSchemaProvider).repository();
 
         final var sourceIdentifier = new SourceIdentifier("testID");
 
@@ -655,11 +655,11 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
     }
 
     private ActorRef registerSlaveMountPoint() {
-        SchemaResourcesDTO schemaResourceDTO2 = mock(SchemaResourcesDTO.class);
-        doReturn(mockRegistry).when(schemaResourceDTO2).getSchemaRegistry();
-        doReturn(mockSchemaRepository).when(schemaResourceDTO2).getSchemaRepository();
+        var deviceSchemaProvider2 = mock(DeviceNetconfSchemaProvider.class);
+        doReturn(mockRegistry).when(deviceSchemaProvider2).registry();
+        doReturn(mockSchemaRepository).when(deviceSchemaProvider2).repository();
         final ActorRef slaveRef = system.actorOf(NetconfNodeActor.props(NetconfTopologySetup.builder()
-                .setSchemaResourceDTO(schemaResourceDTO2)
+                .setDeviceSchemaProvider(deviceSchemaProvider2)
                 .setActorSystem(system)
                 .setBaseSchemaProvider(BASE_SCHEMAS)
                 .build(), remoteDeviceId, TIMEOUT, mockMountPointService));
index 168bd613503f357ae6de9843fc1cb96850c61cde..69d02be4786b02196c4008859a69f97ca7bce39c 100644 (file)
@@ -56,7 +56,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMNotificationService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
@@ -162,9 +162,8 @@ public class NetconfNodeManagerTest extends AbstractBaseSchemasTest {
         NetconfTopologySetup masterSetup = NetconfTopologySetup.builder()
                 .setActorSystem(masterSystem)
                 .setDataBroker(mockDataBroker)
-                .setSchemaResourceDTO(new NetconfDevice.SchemaResourcesDTO(
-                    masterSchemaRepository, masterSchemaRepository, mockSchemaContextFactory, mockSchemasResolver))
                 .setBaseSchemaProvider(BASE_SCHEMAS)
+                .setDeviceSchemaProvider(createDeviceSchemaProvider(masterSchemaRepository))
                 .build();
 
         testMasterActorRef = TestActorRef.create(masterSystem, Props.create(TestMasterActor.class, masterSetup,
@@ -178,9 +177,8 @@ public class NetconfNodeManagerTest extends AbstractBaseSchemasTest {
         NetconfTopologySetup slaveSetup = NetconfTopologySetup.builder()
                 .setActorSystem(slaveSystem)
                 .setDataBroker(mockDataBroker)
-                .setSchemaResourceDTO(new NetconfDevice.SchemaResourcesDTO(
-                    slaveSchemaRepository, slaveSchemaRepository, mockSchemaContextFactory, mockSchemasResolver))
                 .setBaseSchemaProvider(BASE_SCHEMAS)
+                .setDeviceSchemaProvider(createDeviceSchemaProvider(slaveSchemaRepository))
                 .build();
 
         netconfNodeManager = new NetconfNodeManager(slaveSetup, DEVICE_ID, responseTimeout,
@@ -189,6 +187,13 @@ public class NetconfNodeManagerTest extends AbstractBaseSchemasTest {
         setupMountPointMocks();
     }
 
+    private static DeviceNetconfSchemaProvider createDeviceSchemaProvider(final SharedSchemaRepository repository) {
+        final var provider = mock(DeviceNetconfSchemaProvider.class);
+        doReturn(repository).when(provider).registry();
+        doReturn(repository).when(provider).repository();
+        return provider;
+    }
+
     @After
     public void teardown() {
         TestKit.shutdownActorSystem(slaveSystem, true);
index 6ec5205a4eb9af26cf33bb2da79b337ca2b4c71e..de77e29a2f399e4171dd2cde63842fa8b1b12b0f 100644 (file)
@@ -28,13 +28,13 @@ import org.opendaylight.netconf.client.NetconfClientSession;
 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
 import org.opendaylight.netconf.client.mdsal.LibraryModulesSchemas;
 import org.opendaylight.netconf.client.mdsal.LibrarySchemaSourceProvider;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
 import org.opendaylight.netconf.client.mdsal.NetconfDeviceBuilder;
 import org.opendaylight.netconf.client.mdsal.NetconfDeviceCommunicator;
 import org.opendaylight.netconf.client.mdsal.NetconfDeviceSchema;
 import org.opendaylight.netconf.client.mdsal.SchemalessNetconfDevice;
 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDevice;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
@@ -173,12 +173,12 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re
             final var resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(), nodeId.getValue());
             device = new NetconfDeviceBuilder()
                 .setReconnectOnSchemasChange(node.requireReconnectOnChangedSchema())
-                .setSchemaResourcesDTO(resources)
-                .setGlobalProcessingExecutor(schemaAssembler.executor())
+                .setBaseSchemaProvider(baseSchemaProvider)
+                .setDeviceSchemaProvider(resources)
+                .setProcessingExecutor(schemaAssembler.executor())
                 .setId(deviceId)
                 .setSalFacade(salFacade)
                 .setDeviceActionFactory(deviceActionFactory)
-                .setBaseSchemas(baseSchemaProvider)
                 .build();
             yanglibRegistrations = registerDeviceSchemaSources(deviceId, node, resources);
         }
@@ -338,14 +338,13 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re
     }
 
     private static List<Registration> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
-            final NetconfNode node, final SchemaResourcesDTO resources) {
+            final NetconfNode node, final DeviceNetconfSchemaProvider resources) {
         final var yangLibrary = node.getYangLibrary();
         if (yangLibrary != null) {
             final Uri uri = yangLibrary.getYangLibraryUrl();
             if (uri != null) {
                 final var registrations = new ArrayList<Registration>();
                 final var yangLibURL = uri.getValue();
-                final var schemaRegistry = resources.getSchemaRegistry();
 
                 // pre register yang library sources as fallback schemas to schema registry
                 final var yangLibUsername = yangLibrary.getUsername();
@@ -354,8 +353,9 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re
                     ? LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword)
                         : LibraryModulesSchemas.create(yangLibURL);
 
+                final var registry = resources.registry();
                 for (var entry : schemas.getAvailableModels().entrySet()) {
-                    registrations.add(schemaRegistry.registerSchemaSource(new LibrarySchemaSourceProvider(
+                    registrations.add(registry.registerSchemaSource(new LibrarySchemaSourceProvider(
                         remoteDeviceId, schemas.getAvailableModels()),
                         PotentialSchemaSource.create(entry.getKey(), YangTextSource.class,
                             PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
index afbfb8325442bbff4192cb3deda3fb08dc7c3b52..70c661fae322544d56234fcf0ff25c4c9d39ca1d 100644 (file)
@@ -46,9 +46,9 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
 import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil;
-import org.opendaylight.netconf.client.mdsal.spi.NetconfDeviceRpc;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.Get;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesState;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangLibrary;
@@ -126,7 +126,7 @@ public final class LibraryModulesSchemas implements NetconfDeviceSchemas {
     }
 
     // FIXME: this should work on NetconfRpcService
-    public static ListenableFuture<LibraryModulesSchemas> forDevice(final NetconfDeviceRpc deviceRpc,
+    public static ListenableFuture<LibraryModulesSchemas> forDevice(final NetconfRpcService deviceRpc,
             final RemoteDeviceId deviceId) {
         final var future = SettableFuture.<LibraryModulesSchemas>create();
         Futures.addCallback(deviceRpc.invokeNetconf(Get.QNAME, GET_MODULES_STATE_MODULE_LIST_RPC),
index 4c50943c451f290dc28228de407adcd1c3a98092..6ca3b000bf25223a0b7a4cc8e4208615c862e407 100644 (file)
@@ -16,7 +16,7 @@ import com.google.common.util.concurrent.MoreExecutors;
 import java.util.Optional;
 import javax.xml.transform.dom.DOMSource;
 import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.GetSchema;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.Yang;
@@ -53,10 +53,10 @@ public final class MonitoringSchemaSourceProvider implements SchemaSourceProvide
     private static final LeafNode<?> FORMAT_LEAF = ImmutableNodes.leafNode(FORMAT_PATHARG, Yang.QNAME);
     private static final NodeIdentifier NETCONF_DATA_PATHARG = NodeIdentifier.create(Data.QNAME);
 
-    private final DOMRpcService rpc;
+    private final NetconfRpcService rpc;
     private final RemoteDeviceId id;
 
-    public MonitoringSchemaSourceProvider(final RemoteDeviceId id, final DOMRpcService rpc) {
+    public MonitoringSchemaSourceProvider(final RemoteDeviceId id, final NetconfRpcService rpc) {
         this.id = requireNonNull(id);
         this.rpc = requireNonNull(rpc);
     }
@@ -90,12 +90,13 @@ public final class MonitoringSchemaSourceProvider implements SchemaSourceProvide
     @Override
     public ListenableFuture<YangTextSource> getSource(final SourceIdentifier sourceIdentifier) {
         final String moduleName = sourceIdentifier.name().getLocalName();
+        final Revision revision = sourceIdentifier.revision();
+        final ContainerNode getSchemaRequest = createGetSchemaRequest(moduleName,
+            revision == null ? Optional.empty() : Optional.of(revision.toString()));
 
-        final Optional<String> revision = Optional.ofNullable(sourceIdentifier.revision()).map(Revision::toString);
-        final ContainerNode getSchemaRequest = createGetSchemaRequest(moduleName, revision);
         LOG.trace("{}: Loading YANG schema source for {}:{}", id, moduleName, revision);
-        return Futures.transform(
-            rpc.invokeRpc(GetSchema.QNAME, getSchemaRequest), result -> {
+        return Futures.transform(rpc.invokeNetconf(GetSchema.QNAME, getSchemaRequest),
+            result -> {
                 // Transform composite node to string schema representation and then to ASTSchemaSource.
                 if (result.errors().isEmpty()) {
                     final String schemaString = getSchemaFromRpc(id, result.value())
index 8260b755f19a9c114bbb52d12771e548c2a90a05..b743a1e91ae5777fdc3ad1f45f3d8e3ef33e6eba 100644 (file)
@@ -7,42 +7,24 @@
  */
 package org.opendaylight.netconf.client.mdsal;
 
-import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
 import static org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil.NETCONF_GET_NODEID;
 
-import com.google.common.base.Predicates;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
-import com.google.common.util.concurrent.SettableFuture;
-import java.io.Serial;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
 import org.checkerframework.checker.lock.qual.GuardedBy;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
-import org.opendaylight.netconf.api.CapabilityURN;
 import org.opendaylight.netconf.api.messages.NetconfMessage;
 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchema;
 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
-import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDevice;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceCommunicator;
@@ -57,11 +39,6 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.re
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscription;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714.CreateSubscriptionInput;
 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.device.rev240120.connection.oper.available.capabilities.AvailableCapability;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.unavailable.capabilities.UnavailableCapability;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.unavailable.capabilities.UnavailableCapability.FailureReason;
-import org.opendaylight.yangtools.concepts.Registration;
 import org.opendaylight.yangtools.rfc8528.model.api.SchemaMountConstants;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -69,13 +46,6 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdent
 import org.opendaylight.yangtools.yang.data.api.schema.MountPointContext;
 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
-import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
-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.SchemaRepository;
-import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
-import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -84,50 +54,43 @@ import org.slf4j.LoggerFactory;
  */
 public class NetconfDevice implements RemoteDevice<NetconfDeviceCommunicator> {
     private static final Logger LOG = LoggerFactory.getLogger(NetconfDevice.class);
-
     private static final QName RFC8528_SCHEMA_MOUNTS_QNAME = QName.create(
         SchemaMountConstants.RFC8528_MODULE, "schema-mounts").intern();
     private static final YangInstanceIdentifier RFC8528_SCHEMA_MOUNTS = YangInstanceIdentifier.of(
         NodeIdentifier.create(RFC8528_SCHEMA_MOUNTS_QNAME));
 
     protected final RemoteDeviceId id;
-    protected final EffectiveModelContextFactory schemaContextFactory;
-    protected final SchemaSourceRegistry schemaRegistry;
-    protected final SchemaRepository schemaRepository;
-
-    protected final List<Registration> sourceRegistrations = new ArrayList<>();
+    private final BaseNetconfSchemaProvider baseSchemaProvider;
+    private final DeviceNetconfSchemaProvider deviceSchemaProvider;
+    private final Executor processingExecutor;
 
     private final RemoteDeviceHandler salFacade;
-    private final Executor processingExecutor;
     private final DeviceActionFactory deviceActionFactory;
-    private final NetconfDeviceSchemasResolver stateSchemasResolver;
     private final NotificationHandler notificationHandler;
     private final boolean reconnectOnSchemasChange;
-    private final BaseNetconfSchemaProvider baseSchemas;
 
     @GuardedBy("this")
-    private ListenableFuture<List<Object>> schemaFuturesList;
+    private ListenableFuture<?> schemaFuture;
     @GuardedBy("this")
     private boolean connected = false;
 
-    public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final BaseNetconfSchemaProvider baseSchemas,
-            final RemoteDeviceId id, final RemoteDeviceHandler salFacade, final Executor globalProcessingExecutor,
-            final boolean reconnectOnSchemasChange) {
-        this(schemaResourcesDTO, baseSchemas, id, salFacade, globalProcessingExecutor, reconnectOnSchemasChange, null);
+    public NetconfDevice(final RemoteDeviceId id,final BaseNetconfSchemaProvider baseSchemaProvider,
+            final DeviceNetconfSchemaProvider deviceSchemaProvider, final RemoteDeviceHandler salFacade,
+            final Executor globalProcessingExecutor, final boolean reconnectOnSchemasChange) {
+        this(id, baseSchemaProvider, deviceSchemaProvider, salFacade, globalProcessingExecutor,
+            reconnectOnSchemasChange, null);
     }
 
-    public NetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final BaseNetconfSchemaProvider baseSchemas,
-            final RemoteDeviceId id, final RemoteDeviceHandler salFacade, final Executor globalProcessingExecutor,
-            final boolean reconnectOnSchemasChange, final DeviceActionFactory deviceActionFactory) {
-        this.baseSchemas = requireNonNull(baseSchemas);
-        this.id = id;
+    public NetconfDevice(final RemoteDeviceId id, final BaseNetconfSchemaProvider baseSchemaProvider,
+            final DeviceNetconfSchemaProvider deviceSchemaProvider, final RemoteDeviceHandler salFacade,
+            final Executor globalProcessingExecutor, final boolean reconnectOnSchemasChange,
+            final DeviceActionFactory deviceActionFactory) {
+        this.id = requireNonNull(id);
+        this.baseSchemaProvider = requireNonNull(baseSchemaProvider);
+        this.deviceSchemaProvider = requireNonNull(deviceSchemaProvider);
         this.reconnectOnSchemasChange = reconnectOnSchemasChange;
         this.deviceActionFactory = deviceActionFactory;
-        schemaRegistry = schemaResourcesDTO.getSchemaRegistry();
-        schemaRepository = schemaResourcesDTO.getSchemaRepository();
-        schemaContextFactory = schemaResourcesDTO.getSchemaContextFactory();
         this.salFacade = salFacade;
-        stateSchemasResolver = schemaResourcesDTO.getStateSchemasResolver();
         processingExecutor = requireNonNull(globalProcessingExecutor);
         notificationHandler = new NotificationHandler(salFacade, id);
     }
@@ -135,95 +98,54 @@ public class NetconfDevice implements RemoteDevice<NetconfDeviceCommunicator> {
     @Override
     public synchronized void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities,
             final NetconfDeviceCommunicator listener) {
-        // SchemaContext setup has to be performed in a dedicated thread since we are in a Netty thread in this method
+        // EffectiveModelContext setup has to be performed in a dedicated thread since we are in a Netty thread in this
+        // method.
         // YANG models are being downloaded in this method and it would cause a deadlock if we used the netty thread
         // https://netty.io/wiki/thread-model.html
-        setConnected(true);
+        connected = true;
         LOG.debug("{}: Session to remote device established with {}", id, remoteSessionCapabilities);
 
-        final var baseSchema = baseSchemas.baseSchemaForCapabilities(remoteSessionCapabilities);
+        final var baseSchema = baseSchemaProvider.baseSchemaForCapabilities(remoteSessionCapabilities);
         final var initRpc = new NetconfDeviceRpc(baseSchema.modelContext(), listener,
             new NetconfMessageTransformer(baseSchema.mountPointContext(), false, baseSchema));
 
-        // Acquire schemas
-        final var futureSchemas = stateSchemasResolver.resolve(initRpc, remoteSessionCapabilities, id,
-            baseSchema.modelContext());
-
-        // Convert to sources
-        final var sourceResolverFuture = Futures.transform(futureSchemas, availableSchemas -> {
-            final var providedSources = availableSchemas.getAvailableYangSchemasQNames();
-            LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", id, providedSources);
-
-            final var requiredSources = new HashSet<>(remoteSessionCapabilities.moduleBasedCaps().keySet());
-            final var requiredSourcesNotProvided = Sets.difference(requiredSources, providedSources);
-            if (!requiredSourcesNotProvided.isEmpty()) {
-                LOG.warn("{}: Netconf device does not provide all yang models reported in hello message capabilities,"
-                        + " required but not provided: {}", id, requiredSourcesNotProvided);
-                LOG.warn("{}: Attempting to build schema context from required sources", id);
-            }
-
-            // Here all the sources reported in netconf monitoring are merged with those reported in hello.
-            // It is necessary to perform this since submodules are not mentioned in hello but still required.
-            // This clashes with the option of a user to specify supported yang models manually in configuration
-            // for netconf-connector and as a result one is not able to fully override yang models of a device.
-            // It is only possible to add additional models.
-            final var providedSourcesNotRequired = Sets.difference(providedSources, requiredSources);
-            if (!providedSourcesNotRequired.isEmpty()) {
-                LOG.warn("{}: Netconf device provides additional yang models not reported in "
-                        + "hello message capabilities: {}", id, providedSourcesNotRequired);
-                LOG.warn("{}: Adding provided but not required sources as required to prevent failures", id);
-                LOG.debug("{}: Netconf device reported in hello: {}", id, requiredSources);
-                requiredSources.addAll(providedSourcesNotRequired);
-            }
-
-            final var sourceProvider = availableSchemas instanceof LibraryModulesSchemas libraryModule
-                ? new LibrarySchemaSourceProvider(id, libraryModule.getAvailableModels())
-                    : new MonitoringSchemaSourceProvider(id, initRpc.domRpcService());
-            return new DeviceSources(requiredSources, providedSources, sourceProvider);
-        }, MoreExecutors.directExecutor());
-
-        if (shouldListenOnSchemaChange(remoteSessionCapabilities)) {
-            registerToBaseNetconfStream(initRpc, listener);
-        }
-
-        // Set up the EffectiveModelContext for the device
-        final var futureSchema = Futures.transformAsync(sourceResolverFuture,
-            deviceSources -> assembleSchemaContext(deviceSources, remoteSessionCapabilities), processingExecutor);
+        final var deviceSchema = deviceSchemaProvider.deviceNetconfSchemaFor(id, remoteSessionCapabilities, initRpc,
+            baseSchema, processingExecutor);
 
         // Potentially acquire mount point list and interpret it
-        final var netconfDeviceSchemaFuture = Futures.transformAsync(futureSchema,
+        final var netconfDeviceSchemaFuture = Futures.transformAsync(deviceSchema,
             result -> Futures.transform(createMountPointContext(result.modelContext(), baseSchema, listener),
                 mount -> new NetconfDeviceSchema(result.capabilities(), mount), processingExecutor),
             processingExecutor);
-        schemaFuturesList = Futures.allAsList(sourceResolverFuture, futureSchema, netconfDeviceSchemaFuture);
+        schemaFuture = netconfDeviceSchemaFuture;
 
         Futures.addCallback(netconfDeviceSchemaFuture, new FutureCallback<>() {
-                @Override
-                public void onSuccess(final NetconfDeviceSchema result) {
-                    handleSalInitializationSuccess(listener, baseSchema, result, remoteSessionCapabilities,
-                        getDeviceSpecificRpc(result.mountContext(), listener, baseSchema));
-                }
+            @Override
+            public void onSuccess(final NetconfDeviceSchema result) {
+                handleSalInitializationSuccess(listener, baseSchema, result, remoteSessionCapabilities,
+                    getDeviceSpecificRpc(result.mountContext(), listener, baseSchema));
+            }
 
-                @Override
-                public void onFailure(final Throwable cause) {
-                    // The method onRemoteSessionDown was called while the EffectiveModelContext for the device
-                    // was being processed.
-                    if (cause instanceof CancellationException) {
-                        LOG.warn("{}: Device communicator was tear down since the schema setup started", id);
-                    } else {
-                        handleSalInitializationFailure(listener, cause);
-                    }
+            @Override
+            public void onFailure(final Throwable cause) {
+                // The method onRemoteSessionDown was called while the EffectiveModelContext for the device was being
+                // processed.
+                if (cause instanceof CancellationException) {
+                    LOG.warn("{}: Device communicator was tear down since the schema setup started", id);
+                } else {
+                    handleSalInitializationFailure(listener, cause);
                 }
-            }, MoreExecutors.directExecutor());
+            }
+        }, MoreExecutors.directExecutor());
     }
 
-    private void registerToBaseNetconfStream(final NetconfDeviceRpc deviceRpc,
+    private void registerToBaseNetconfStream(final NetconfRpcService 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 var rpcResultListenableFuture = deviceRpc.domRpcService()
-            .invokeRpc(CreateSubscription.QNAME, ImmutableNodes.newContainerBuilder()
+        final var rpcResultListenableFuture = deviceRpc.invokeNetconf(CreateSubscription.QNAME,
+            ImmutableNodes.newContainerBuilder()
                 .withNodeIdentifier(NodeIdentifier.create(CreateSubscriptionInput.QNAME))
                 // Note: default 'stream' is 'NETCONF', we do not need to create an explicit leaf
                 .build());
@@ -255,7 +177,7 @@ public class NetconfDevice implements RemoteDevice<NetconfDeviceCommunicator> {
         return remoteSessionCapabilities.isNotificationsSupported() && reconnectOnSchemasChange;
     }
 
-    private synchronized void handleSalInitializationSuccess(final RemoteDeviceCommunicator listener,
+    private synchronized void handleSalInitializationSuccess(final NetconfDeviceCommunicator listener,
             final BaseNetconfSchema baseSchema, final NetconfDeviceSchema deviceSchema,
             final NetconfSessionPreferences remoteSessionCapabilities, final Rpcs deviceRpc) {
         // NetconfDevice.SchemaSetup can complete after NetconfDeviceCommunicator was closed. In that case do nothing,
@@ -265,6 +187,10 @@ public class NetconfDevice implements RemoteDevice<NetconfDeviceCommunicator> {
             return;
         }
 
+        if (shouldListenOnSchemaChange(remoteSessionCapabilities)) {
+            registerToBaseNetconfStream(deviceRpc, listener);
+        }
+
         final var messageTransformer = new NetconfMessageTransformer(deviceSchema.mountContext(), true, baseSchema);
 
         // Order is important here: salFacade has to see the device come up and then the notificationHandler can deliver
@@ -286,33 +212,16 @@ public class NetconfDevice implements RemoteDevice<NetconfDeviceCommunicator> {
 
     private synchronized void cleanupInitialization() {
         connected = false;
-        if (schemaFuturesList != null && !schemaFuturesList.isDone()) {
-            if (!schemaFuturesList.cancel(true)) {
-                LOG.warn("The cleanup of Schema Futures for device {} was unsuccessful.", id);
-            }
+        if (schemaFuture != null && !schemaFuture.isDone() && !schemaFuture.cancel(true)) {
+            LOG.warn("The cleanup of Schema Futures for device {} was unsuccessful.", id);
         }
         notificationHandler.onRemoteSchemaDown();
-        sourceRegistrations.forEach(Registration::close);
-        sourceRegistrations.clear();
-    }
-
-    private synchronized void setConnected(final boolean connected) {
-        this.connected = connected;
-    }
-
-    private ListenableFuture<SchemaResult> assembleSchemaContext(final DeviceSources deviceSources,
-            final NetconfSessionPreferences remoteSessionCapabilities) {
-        LOG.debug("{}: Resolved device sources to {}", id, deviceSources);
-
-        sourceRegistrations.addAll(deviceSources.register(schemaRegistry));
-
-        return new SchemaSetup(deviceSources, remoteSessionCapabilities).startResolution();
     }
 
     private ListenableFuture<@NonNull MountPointContext> createMountPointContext(
             final EffectiveModelContext schemaContext, final BaseNetconfSchema baseSchema,
             final NetconfDeviceCommunicator listener) {
-        final MountPointContext emptyContext = MountPointContext.of(schemaContext);
+        final var emptyContext = MountPointContext.of(schemaContext);
         if (schemaContext.findModule(SchemaMountConstants.RFC8528_MODULE).isEmpty()) {
             return Futures.immediateFuture(emptyContext);
         }
@@ -359,258 +268,15 @@ public class NetconfDevice implements RemoteDevice<NetconfDeviceCommunicator> {
             new NetconfMessageTransformer(result, true, schema));
     }
 
-    /**
-     * Just a transfer object containing schema related dependencies. Injected in constructor.
-     */
-    public static class SchemaResourcesDTO {
-        private final SchemaSourceRegistry schemaRegistry;
-        private final SchemaRepository schemaRepository;
-        private final EffectiveModelContextFactory schemaContextFactory;
-        private final NetconfDeviceSchemasResolver stateSchemasResolver;
-
-        public SchemaResourcesDTO(final SchemaSourceRegistry schemaRegistry,
-                                  final SchemaRepository schemaRepository,
-                                  final EffectiveModelContextFactory schemaContextFactory,
-                                  final NetconfDeviceSchemasResolver deviceSchemasResolver) {
-            this.schemaRegistry = requireNonNull(schemaRegistry);
-            this.schemaRepository = requireNonNull(schemaRepository);
-            this.schemaContextFactory = requireNonNull(schemaContextFactory);
-            stateSchemasResolver = requireNonNull(deviceSchemasResolver);
-        }
-
-        public SchemaSourceRegistry getSchemaRegistry() {
-            return schemaRegistry;
-        }
-
-        public SchemaRepository getSchemaRepository() {
-            return schemaRepository;
-        }
-
-        public EffectiveModelContextFactory getSchemaContextFactory() {
-            return schemaContextFactory;
-        }
-
-        public NetconfDeviceSchemasResolver getStateSchemasResolver() {
-            return stateSchemasResolver;
-        }
-    }
-
     /**
      * A dedicated exception to indicate when we fail to setup an {@link EffectiveModelContext}.
      */
     public static final class EmptySchemaContextException extends Exception {
-        @Serial
+        @java.io.Serial
         private static final long serialVersionUID = 1L;
 
         public EmptySchemaContextException(final String message) {
             super(message);
         }
     }
-
-    /**
-     * {@link NetconfDeviceCapabilities} and {@link EffectiveModelContext}.
-     */
-    private record SchemaResult(
-        @NonNull NetconfDeviceCapabilities capabilities,
-        @NonNull EffectiveModelContext modelContext) {
-
-        SchemaResult {
-            requireNonNull(capabilities);
-            requireNonNull(modelContext);
-        }
-    }
-
-    /**
-     * Schema builder that tries to build schema context from provided sources or biggest subset of it.
-     */
-    private final class SchemaSetup implements FutureCallback<EffectiveModelContext> {
-        private final SettableFuture<SchemaResult> resultFuture = SettableFuture.create();
-
-        private final Set<AvailableCapability> nonModuleBasedCapabilities = new HashSet<>();
-        private final Map<QName, FailureReason> unresolvedCapabilites = new HashMap<>();
-        private final Set<AvailableCapability> resolvedCapabilities = new HashSet<>();
-
-        private final DeviceSources deviceSources;
-        private final NetconfSessionPreferences remoteSessionCapabilities;
-
-        private Collection<SourceIdentifier> requiredSources;
-
-        SchemaSetup(final DeviceSources deviceSources, final NetconfSessionPreferences remoteSessionCapabilities) {
-            this.deviceSources = deviceSources;
-            this.remoteSessionCapabilities = remoteSessionCapabilities;
-
-            // If device supports notifications and does not contain necessary modules, add them automatically
-            if (remoteSessionCapabilities.containsNonModuleCapability(CapabilityURN.NOTIFICATION)) {
-                // FIXME: mutable collection modification!
-                deviceSources.getRequiredSourcesQName().addAll(List.of(
-                    org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
-                        .YangModuleInfoImpl.getInstance().getName(),
-                    org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
-                        .YangModuleInfoImpl.getInstance().getName())
-                );
-            }
-
-            requiredSources = deviceSources.getRequiredSources();
-            final Collection<SourceIdentifier> missingSources = filterMissingSources(requiredSources);
-
-            addUnresolvedCapabilities(getQNameFromSourceIdentifiers(missingSources),
-                UnavailableCapability.FailureReason.MissingSource);
-            requiredSources.removeAll(missingSources);
-        }
-
-        ListenableFuture<SchemaResult> startResolution() {
-            trySetupSchema();
-            return resultFuture;
-        }
-
-        @Override
-        public void onSuccess(final EffectiveModelContext result) {
-            LOG.debug("{}: Schema context built successfully from {}", id, requiredSources);
-
-            final Collection<QName> filteredQNames = Sets.difference(deviceSources.getRequiredSourcesQName(),
-                    unresolvedCapabilites.keySet());
-            resolvedCapabilities.addAll(filteredQNames.stream()
-                .map(capability -> new AvailableCapabilityBuilder()
-                    .setCapability(capability.toString())
-                    .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability))
-                    .build())
-                .collect(Collectors.toList()));
-
-            nonModuleBasedCapabilities.addAll(remoteSessionCapabilities.nonModuleCaps().keySet().stream()
-                .map(capability -> new AvailableCapabilityBuilder()
-                    .setCapability(capability)
-                    .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability))
-                    .build())
-                .collect(Collectors.toList()));
-
-
-            resultFuture.set(new SchemaResult(new NetconfDeviceCapabilities(ImmutableMap.copyOf(unresolvedCapabilites),
-                ImmutableSet.copyOf(resolvedCapabilities), ImmutableSet.copyOf(nonModuleBasedCapabilities)), result));
-        }
-
-        @Override
-        public void onFailure(final Throwable cause) {
-            // 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 (cause instanceof MissingSchemaSourceException) {
-                requiredSources = handleMissingSchemaSourceException((MissingSchemaSourceException) cause);
-            } else if (cause instanceof SchemaResolutionException) {
-                requiredSources = handleSchemaResolutionException((SchemaResolutionException) cause);
-            } else {
-                LOG.debug("Unhandled failure", cause);
-                resultFuture.setException(cause);
-                // No more trying...
-                return;
-            }
-
-            trySetupSchema();
-        }
-
-        private void trySetupSchema() {
-            if (!requiredSources.isEmpty()) {
-                // Initiate async resolution, drive it back based on the result
-                LOG.trace("{}: Trying to build schema context from {}", id, requiredSources);
-                Futures.addCallback(schemaContextFactory.createEffectiveModelContext(requiredSources), this,
-                    MoreExecutors.directExecutor());
-            } else {
-                LOG.debug("{}: no more sources for schema context", id);
-                resultFuture.setException(new EmptySchemaContextException(id + ": No more sources for schema context"));
-            }
-        }
-
-        private List<SourceIdentifier> filterMissingSources(final Collection<SourceIdentifier> origSources) {
-            return origSources.parallelStream().filter(sourceIdentifier -> {
-                try {
-                    schemaRepository.getSchemaSource(sourceIdentifier, YangTextSource.class).get();
-                    return false;
-                } catch (InterruptedException | ExecutionException e) {
-                    return true;
-                }
-            }).collect(Collectors.toList());
-        }
-
-        private void addUnresolvedCapabilities(final Collection<QName> capabilities, final FailureReason reason) {
-            for (QName s : capabilities) {
-                unresolvedCapabilites.put(s, reason);
-            }
-        }
-
-        private List<SourceIdentifier> handleMissingSchemaSourceException(
-                final MissingSchemaSourceException exception) {
-            // In case source missing, try without it
-            final SourceIdentifier missingSource = exception.sourceId();
-            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",
-                id, missingSource, exception);
-            final var qNameOfMissingSource = getQNameFromSourceIdentifiers(Sets.newHashSet(missingSource));
-            if (!qNameOfMissingSource.isEmpty()) {
-                addUnresolvedCapabilities(qNameOfMissingSource, UnavailableCapability.FailureReason.MissingSource);
-            }
-            return stripUnavailableSource(missingSource);
-        }
-
-        private Collection<SourceIdentifier> handleSchemaResolutionException(
-                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?
-            final var failedSourceId = resolutionException.sourceId();
-            if (failedSourceId != null) {
-                // flawed model - exclude it
-                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, failedSourceId, resolutionException);
-                addUnresolvedCapabilities(getQNameFromSourceIdentifiers(List.of(failedSourceId)),
-                        UnavailableCapability.FailureReason.UnableToResolve);
-                return stripUnavailableSource(failedSourceId);
-            }
-            // unsatisfied imports
-            addUnresolvedCapabilities(
-                getQNameFromSourceIdentifiers(resolutionException.getUnsatisfiedImports().keySet()),
-                UnavailableCapability.FailureReason.UnableToResolve);
-            LOG.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
-                id, resolutionException.getUnsatisfiedImports());
-            LOG.debug("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
-                id, resolutionException.getUnsatisfiedImports(), resolutionException);
-            return resolutionException.getResolvedSources();
-        }
-
-        private List<SourceIdentifier> stripUnavailableSource(final SourceIdentifier sourceIdToRemove) {
-            final var tmp = new ArrayList<>(requiredSources);
-            checkState(tmp.remove(sourceIdToRemove), "%s: Trying to remove %s from %s failed", id, sourceIdToRemove,
-                requiredSources);
-            return tmp;
-        }
-
-        private Collection<QName> getQNameFromSourceIdentifiers(final Collection<SourceIdentifier> identifiers) {
-            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);
-            }
-            return Collections2.filter(qNames, Predicates.notNull());
-        }
-
-        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.name().getLocalName())) {
-                    continue;
-                }
-
-                if (Objects.equals(identifier.revision(), qname.getRevision().orElse(null))) {
-                    return qname;
-                }
-            }
-            LOG.warn("Unable to map identifier to a devices reported capability: {} Available: {}",identifier,
-                    deviceSources.getRequiredSourcesQName());
-            // return null since we cannot find the QName,
-            // this capability will be removed from required sources and not reported as unresolved-capability
-            return null;
-        }
-    }
 }
index e97b231317dcb9d2a2cb96ead782e2b7ab68f7fd..40701654a88ce1648920d7c177b19148bf06445a 100644 (file)
@@ -10,67 +10,67 @@ package org.opendaylight.netconf.client.mdsal;
 import static java.util.Objects.requireNonNull;
 
 import java.util.concurrent.Executor;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 
-public class NetconfDeviceBuilder {
+// FIXME: everything except DeviceActionFactory is mandatory, hence this builder is a superfluous indirection
+public final class NetconfDeviceBuilder {
     private boolean reconnectOnSchemasChange;
-    private SchemaResourcesDTO schemaResourcesDTO;
+    private DeviceNetconfSchemaProvider deviceSchemaProvider;
     private RemoteDeviceId id;
     private RemoteDeviceHandler salFacade;
-    private Executor globalProcessingExecutor;
+    // FIXME: this should not be here
+    private Executor processingExecutor;
     private DeviceActionFactory deviceActionFactory;
-    private BaseNetconfSchemaProvider baseSchemas;
+    private BaseNetconfSchemaProvider baseSchemaProvider;
 
-    public NetconfDeviceBuilder setReconnectOnSchemasChange(final boolean reconnectOnSchemasChange) {
+    public @NonNull NetconfDeviceBuilder setReconnectOnSchemasChange(final boolean reconnectOnSchemasChange) {
         this.reconnectOnSchemasChange = reconnectOnSchemasChange;
         return this;
     }
 
-    public NetconfDeviceBuilder setId(final RemoteDeviceId id) {
-        this.id = id;
+    public @NonNull NetconfDeviceBuilder setId(final RemoteDeviceId id) {
+        this.id = requireNonNull(id);
         return this;
     }
 
-    public NetconfDeviceBuilder setSchemaResourcesDTO(final SchemaResourcesDTO schemaResourcesDTO) {
-        this.schemaResourcesDTO = schemaResourcesDTO;
+    public @NonNull NetconfDeviceBuilder setSalFacade(final RemoteDeviceHandler salFacade) {
+        this.salFacade = requireNonNull(salFacade);
         return this;
     }
 
-    public NetconfDeviceBuilder setSalFacade(final RemoteDeviceHandler salFacade) {
-        this.salFacade = salFacade;
+    public @NonNull NetconfDeviceBuilder setProcessingExecutor(final Executor processingExecutor) {
+        this.processingExecutor = requireNonNull(processingExecutor);
         return this;
     }
 
-    public NetconfDeviceBuilder setGlobalProcessingExecutor(final Executor globalProcessingExecutor) {
-        this.globalProcessingExecutor = globalProcessingExecutor;
-        return this;
-    }
-
-    public NetconfDeviceBuilder setDeviceActionFactory(final DeviceActionFactory deviceActionFactory) {
+    public @NonNull NetconfDeviceBuilder setDeviceActionFactory(final DeviceActionFactory deviceActionFactory) {
         this.deviceActionFactory = deviceActionFactory;
         return this;
     }
 
-    public NetconfDeviceBuilder setBaseSchemas(final BaseNetconfSchemaProvider baseSchemas) {
-        this.baseSchemas = requireNonNull(baseSchemas);
+    public @NonNull NetconfDeviceBuilder setBaseSchemaProvider(final BaseNetconfSchemaProvider baseSchemaProvider) {
+        this.baseSchemaProvider = requireNonNull(baseSchemaProvider);
         return this;
     }
 
-    public NetconfDevice build() {
-        validation();
-        return new NetconfDevice(schemaResourcesDTO, baseSchemas, id, salFacade,
-            globalProcessingExecutor, reconnectOnSchemasChange, deviceActionFactory);
+    public @NonNull NetconfDeviceBuilder setDeviceSchemaProvider(
+            final DeviceNetconfSchemaProvider deviceSchemaProvider) {
+        this.deviceSchemaProvider = requireNonNull(deviceSchemaProvider);
+        return this;
     }
 
-    private void validation() {
-        requireNonNull(baseSchemas, "BaseSchemas is not initialized");
-        requireNonNull(id, "RemoteDeviceId is not initialized");
-        requireNonNull(salFacade, "RemoteDeviceHandler is not initialized");
-        requireNonNull(globalProcessingExecutor, "ExecutorService is not initialized");
-        requireNonNull(schemaResourcesDTO, "SchemaResourceDTO is not initialized");
+    public @NonNull NetconfDevice build() {
+        return new NetconfDevice(
+            requireNonNull(id, "RemoteDeviceId is not initialized"),
+            requireNonNull(baseSchemaProvider, "BaseNetconfSchemaProvider is not initialized"),
+            requireNonNull(deviceSchemaProvider, "DeviceNetconfSchemaProvider is not initialized"),
+            requireNonNull(salFacade, "RemoteDeviceHandler is not initialized"),
+            requireNonNull(processingExecutor, "Executor is not initialized"),
+            reconnectOnSchemasChange, deviceActionFactory);
     }
 }
index 4805400158bcef32a67b1220fb87a934c49d1c94..03db013bc41a1b5a8d4065a91fec62d00fc12e0f 100644 (file)
@@ -11,10 +11,9 @@ import static java.util.Objects.requireNonNull;
 
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.data.api.schema.MountPointContext;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 /**
- * {@link NetconfDeviceCapabilities} and {@link EffectiveModelContext} pertaining to a {@link NetconfDevice}.
+ * {@link NetconfDeviceCapabilities} and {@link MountPointContext} pertaining to a {@link NetconfDevice}.
  */
 public record NetconfDeviceSchema(
     @NonNull NetconfDeviceCapabilities capabilities,
index c8e2ae8349c52e8033862b2e9eb88066223129ef..41b2beefb15e90cf20e8e27744ec19958447078e 100644 (file)
@@ -27,10 +27,10 @@ import javax.xml.transform.dom.DOMSource;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.netconf.api.NamespaceURN;
 import org.opendaylight.netconf.api.xml.XmlUtil;
 import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.netconf.common.mdsal.NormalizedDataUtil;
@@ -118,7 +118,7 @@ public final class NetconfStateSchemas implements NetconfDeviceSchemas {
     /**
      * Issue get request to remote device and parse response to find all schemas under netconf-state/schemas.
      */
-    static ListenableFuture<NetconfStateSchemas> forDevice(final DOMRpcService deviceRpc,
+    static ListenableFuture<NetconfStateSchemas> forDevice(final NetconfRpcService deviceRpc,
             final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id,
             final EffectiveModelContext modelContext) {
         if (!remoteSessionCapabilities.isMonitoringSupported()) {
@@ -129,7 +129,7 @@ public final class NetconfStateSchemas implements NetconfDeviceSchemas {
         }
 
         final var future = SettableFuture.<NetconfStateSchemas>create();
-        Futures.addCallback(deviceRpc.invokeRpc(Get.QNAME, GET_SCHEMAS_RPC),
+        Futures.addCallback(deviceRpc.invokeNetconf(Get.QNAME, GET_SCHEMAS_RPC),
             new FutureCallback<DOMRpcResult>() {
                 @Override
                 public void onSuccess(final DOMRpcResult result) {
index cd6d77283b5f45d43421df227373786646fb7b98..ca8d8d86115785d027c784308e3de23a28e271e9 100644 (file)
@@ -11,9 +11,9 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemas;
 import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
-import org.opendaylight.netconf.client.mdsal.spi.NetconfDeviceRpc;
 import org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangModuleInfoImpl;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
@@ -29,13 +29,12 @@ public final class NetconfStateSchemasResolverImpl implements NetconfDeviceSchem
         .bindTo(QNameModule.create(RFC8525_YANG_LIBRARY_CAPABILITY.getNamespace(), Revision.of("2016-06-21"))).intern();
 
     @Override
-    public ListenableFuture<? extends NetconfDeviceSchemas> resolve(final NetconfDeviceRpc deviceRpc,
+    public ListenableFuture<? extends NetconfDeviceSchemas> resolve(final NetconfRpcService deviceRpc,
             final NetconfSessionPreferences remoteSessionCapabilities, final RemoteDeviceId id,
             final EffectiveModelContext schemaContext) {
         // FIXME: I think we should prefer YANG library here
         if (remoteSessionCapabilities.isMonitoringSupported()) {
-            return NetconfStateSchemas.forDevice(deviceRpc.domRpcService(), remoteSessionCapabilities, id,
-                schemaContext);
+            return NetconfStateSchemas.forDevice(deviceRpc, remoteSessionCapabilities, id, schemaContext);
         }
         if (remoteSessionCapabilities.containsModuleCapability(RFC8525_YANG_LIBRARY_CAPABILITY)
                 || remoteSessionCapabilities.containsModuleCapability(RFC7895_YANG_LIBRARY_CAPABILITY)) {
index ff7883c16f14638331ceb83a37442aa1e83d1f39..5b17882df8883e27d66e5dd25250abf0180708bf 100644 (file)
@@ -23,5 +23,6 @@ public interface BaseNetconfSchemaProvider {
      * @return A {@link BaseNetconfSchema}
      * @throws NullPointerException if {@code capabilityURNs} is {@code null}
      */
+    // FIXME: return ListenableFuture
     BaseNetconfSchema baseSchemaForCapabilities(NetconfSessionPreferences sessionPreferences);
 }
diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/DeviceNetconfSchema.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/DeviceNetconfSchema.java
new file mode 100644 (file)
index 0000000..16fe8a1
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.client.mdsal.api;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.netconf.client.mdsal.NetconfDeviceCapabilities;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+
+/**
+ * {@link NetconfDeviceCapabilities} and {@link EffectiveModelContext}.
+ */
+@NonNullByDefault
+public record DeviceNetconfSchema(NetconfDeviceCapabilities capabilities, EffectiveModelContext modelContext) {
+    public DeviceNetconfSchema {
+        requireNonNull(capabilities);
+        requireNonNull(modelContext);
+    }
+}
\ No newline at end of file
diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/DeviceNetconfSchemaProvider.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/DeviceNetconfSchemaProvider.java
new file mode 100644 (file)
index 0000000..b0b7236
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.client.mdsal.api;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.concurrent.Executor;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
+
+/**
+ * A provider of {@link DeviceNetconfSchema}.
+ */
+@NonNullByDefault
+public interface DeviceNetconfSchemaProvider {
+
+    ListenableFuture<DeviceNetconfSchema> deviceNetconfSchemaFor(RemoteDeviceId deviceId,
+        NetconfSessionPreferences sessionPreferences, NetconfRpcService deviceRpc, BaseNetconfSchema baseSchema,
+        // FIXME: this parameter should not be here
+        Executor processingExecutor);
+
+    // FIXME: These support:
+    //        - external URL-based pre-registration of schema sources from topology, which should really be catered
+    //          through deviceNetconfSchemaFor() with the sources being registered only for the duration of schema
+    //          assembly
+    //        - netconf-topology-singleton lifecycle, which needs to be carefully examined
+    @Deprecated
+    SchemaRepository repository();
+
+    @Deprecated
+    SchemaSourceRegistry registry();
+
+    @Deprecated
+    EffectiveModelContextFactory contextFactory();
+}
index 4142a666e928d011bc2e7ae7f6f083885e7001b1..e72dbb5a88cbb70ea28b55fb9f9d85c03ecd2f21 100644 (file)
@@ -8,7 +8,6 @@
 package org.opendaylight.netconf.client.mdsal.api;
 
 import com.google.common.util.concurrent.ListenableFuture;
-import org.opendaylight.netconf.client.mdsal.spi.NetconfDeviceRpc;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
 /**
@@ -16,6 +15,6 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
  */
 public interface NetconfDeviceSchemasResolver {
     // FIXME: document this method
-    ListenableFuture<? extends NetconfDeviceSchemas> resolve(NetconfDeviceRpc deviceRpc,
+    ListenableFuture<? extends NetconfDeviceSchemas> resolve(NetconfRpcService deviceRpc,
         NetconfSessionPreferences remoteSessionCapabilities, RemoteDeviceId id, EffectiveModelContext schemaContext);
 }
index c41bea78008047ba19f2a1671ed710a5099fa3d2..82ec3d2acac88341b46c1bd1832538d8d9666582 100644 (file)
@@ -9,15 +9,13 @@ package org.opendaylight.netconf.client.mdsal.api;
 
 import com.google.common.annotations.Beta;
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
 
 @Beta
 @NonNullByDefault
 public interface SchemaResourceManager {
-
     // FIXME: document this, nodeId is not quite appropriate name here: it should be a @NonNull id with .toString()
     //        being interesting
-    // FIXME: subDirectory should have be really String..., placing the onus of splitting the directory to callers,
-    //        so we do not get separator ambiguity
-    SchemaResourcesDTO getSchemaResources(String subDirectory, Object nodeId);
+    // FIXME: subDirectory should have be really 'String...' or a 'java.nio.file.Path', placing the onus of splitting
+    //        the directory to callers, so we do not get separator ambiguity
+    DeviceNetconfSchemaProvider getSchemaResources(String subDirectory, Object nodeId);
 }
diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultDeviceNetconfSchemaProvider.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultDeviceNetconfSchemaProvider.java
new file mode 100644 (file)
index 0000000..a65aaba
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.client.mdsal.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.netconf.client.mdsal.LibraryModulesSchemas;
+import org.opendaylight.netconf.client.mdsal.LibrarySchemaSourceProvider;
+import org.opendaylight.netconf.client.mdsal.MonitoringSchemaSourceProvider;
+import org.opendaylight.netconf.client.mdsal.NetconfStateSchemasResolverImpl;
+import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchema;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
+import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
+import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
+import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
+import org.opendaylight.yangtools.concepts.Registration;
+import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
+import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@VisibleForTesting
+public final class DefaultDeviceNetconfSchemaProvider implements DeviceNetconfSchemaProvider {
+    private static final Logger LOG = LoggerFactory.getLogger(DefaultDeviceNetconfSchemaProvider.class);
+
+    // FIXME: resolver seems to be a useless indirection
+    private final NetconfDeviceSchemasResolver resolver;
+    private final @NonNull EffectiveModelContextFactory contextFactory;
+    private final @NonNull SchemaSourceRegistry registry;
+    private final @NonNull SchemaRepository repository;
+    // FIXME: private final Executor processingExecutor;
+
+    DefaultDeviceNetconfSchemaProvider(final SharedSchemaRepository repository) {
+        this(repository, repository,
+            repository.createEffectiveModelContextFactory(SchemaContextFactoryConfiguration.getDefault()),
+            new NetconfStateSchemasResolverImpl());
+    }
+
+    @VisibleForTesting
+    public DefaultDeviceNetconfSchemaProvider(final SchemaSourceRegistry registry, final SchemaRepository repository,
+            final EffectiveModelContextFactory contextFactory, final NetconfDeviceSchemasResolver resolver) {
+        this.registry = requireNonNull(registry);
+        this.repository = requireNonNull(repository);
+        this.contextFactory = requireNonNull(contextFactory);
+        this.resolver = requireNonNull(resolver);
+    }
+
+    @Override
+    public ListenableFuture<DeviceNetconfSchema> deviceNetconfSchemaFor(final RemoteDeviceId deviceId,
+            final NetconfSessionPreferences sessionPreferences, final NetconfRpcService deviceRpc,
+            final BaseNetconfSchema baseSchema, final Executor processingExecutor) {
+
+        // Acquire schemas
+        final var futureSchemas = resolver.resolve(deviceRpc, sessionPreferences, deviceId, baseSchema.modelContext());
+
+        // Convert to sources
+        final var sourceResolverFuture = Futures.transform(futureSchemas, availableSchemas -> {
+            final var providedSources = availableSchemas.getAvailableYangSchemasQNames();
+            LOG.debug("{}: Schemas exposed by ietf-netconf-monitoring: {}", deviceId, providedSources);
+
+            final var requiredSources = new HashSet<>(sessionPreferences.moduleBasedCaps().keySet());
+            final var requiredSourcesNotProvided = Sets.difference(requiredSources, providedSources);
+            if (!requiredSourcesNotProvided.isEmpty()) {
+                LOG.warn("{}: Netconf device does not provide all yang models reported in hello message capabilities,"
+                        + " required but not provided: {}", deviceId, requiredSourcesNotProvided);
+                LOG.warn("{}: Attempting to build schema context from required sources", deviceId);
+            }
+
+            // Here all the sources reported in netconf monitoring are merged with those reported in hello.
+            // It is necessary to perform this since submodules are not mentioned in hello but still required.
+            // This clashes with the option of a user to specify supported yang models manually in configuration
+            // for netconf-connector and as a result one is not able to fully override yang models of a device.
+            // It is only possible to add additional models.
+            final var providedSourcesNotRequired = Sets.difference(providedSources, requiredSources);
+            if (!providedSourcesNotRequired.isEmpty()) {
+                LOG.warn("{}: Netconf device provides additional yang models not reported in "
+                        + "hello message capabilities: {}", deviceId, providedSourcesNotRequired);
+                LOG.warn("{}: Adding provided but not required sources as required to prevent failures", deviceId);
+                LOG.debug("{}: Netconf device reported in hello: {}", deviceId, requiredSources);
+                requiredSources.addAll(providedSourcesNotRequired);
+            }
+
+            // FIXME: this instanceof check is quite bad
+            final var sourceProvider = availableSchemas instanceof LibraryModulesSchemas libraryModule
+                ? new LibrarySchemaSourceProvider(deviceId, libraryModule.getAvailableModels())
+                    : new MonitoringSchemaSourceProvider(deviceId, deviceRpc);
+            return new DeviceSources(requiredSources, providedSources, sourceProvider);
+        }, MoreExecutors.directExecutor());
+
+        // Set up the EffectiveModelContext for the device
+        return Futures.transformAsync(sourceResolverFuture, deviceSources -> {
+            LOG.debug("{}: Resolved device sources to {}", deviceId, deviceSources);
+
+            // Register all sources with repository and start resolution
+            final var registrations = deviceSources.register(registry);
+            final var future = new SchemaSetup(repository, contextFactory, deviceId, deviceSources, sessionPreferences)
+                .startResolution();
+
+            // Unregister sources once resolution is complete
+            future.addListener(() -> registrations.forEach(Registration::close), processingExecutor);
+
+            return future;
+        }, processingExecutor);
+    }
+
+    @Deprecated
+    @Override
+    public SchemaRepository repository() {
+        return repository;
+    }
+
+    @Deprecated
+    @Override
+    public SchemaSourceRegistry registry() {
+        return registry;
+    }
+
+    @Deprecated
+    @Override
+    public EffectiveModelContextFactory contextFactory() {
+        return contextFactory;
+    }
+}
index 0d59f0edfbcfacfa3ece8ace9b2e7d27be2a48cc..5942a5ceacff51afa179a664193812381f0c4df6 100644 (file)
@@ -18,11 +18,9 @@ import javax.inject.Inject;
 import javax.inject.Singleton;
 import org.checkerframework.checker.lock.qual.GuardedBy;
 import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
-import org.opendaylight.netconf.client.mdsal.NetconfStateSchemasResolverImpl;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
 import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
-import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfiguration;
 import org.opendaylight.yangtools.yang.model.repo.fs.FilesystemSchemaSourceCache;
 import org.opendaylight.yangtools.yang.model.repo.spi.SoftSchemaSourceCache;
 import org.opendaylight.yangtools.yang.model.spi.source.YangIRSource;
@@ -49,8 +47,8 @@ public final class DefaultSchemaResourceManager implements SchemaResourceManager
     private static final Logger LOG = LoggerFactory.getLogger(DefaultSchemaResourceManager.class);
 
     @GuardedBy("this")
-    private final Map<String, SchemaResourcesDTO> resources = new HashMap<>();
-    private final @NonNull SchemaResourcesDTO defaultResources;
+    private final Map<String, DeviceNetconfSchemaProvider> resources = new HashMap<>();
+    private final @NonNull DeviceNetconfSchemaProvider defaultResources;
     private final YangParserFactory parserFactory;
     private final String defaultSubdirectory;
     private final String rootDirectory;
@@ -71,7 +69,7 @@ public final class DefaultSchemaResourceManager implements SchemaResourceManager
     }
 
     @Override
-    public SchemaResourcesDTO getSchemaResources(final String subdir, final Object nodeId) {
+    public DeviceNetconfSchemaProvider getSchemaResources(final String subdir, final Object nodeId) {
         if (defaultSubdirectory.equals(subdir)) {
             // Fast path for default devices
             return defaultResources;
@@ -90,21 +88,21 @@ public final class DefaultSchemaResourceManager implements SchemaResourceManager
         return getSchemaResources(subdir);
     }
 
-    private synchronized @NonNull SchemaResourcesDTO getSchemaResources(final String subdir) {
+    private synchronized @NonNull DeviceNetconfSchemaProvider getSchemaResources(final String subdir) {
         // Fast path for unusual devices
-        final SchemaResourcesDTO existing = resources.get(subdir);
+        final var existing = resources.get(subdir);
         if (existing != null) {
             return existing;
         }
 
-        final SchemaResourcesDTO created = createResources(subdir);
+        final var created = createResources(subdir);
         resources.put(subdir, created);
         return created;
     }
 
-    private @NonNull SchemaResourcesDTO createResources(final String subdir) {
+    private @NonNull DeviceNetconfSchemaProvider createResources(final String subdir) {
         // Setup the baseline empty registry
-        final SharedSchemaRepository repository = new SharedSchemaRepository(subdir, parserFactory);
+        final var repository = new SharedSchemaRepository(subdir, parserFactory);
 
         // Teach the registry how to transform YANG text to IRSchemaSource internally
         repository.registerSchemaSourceListener(TextToIRTransformer.create(repository, repository));
@@ -112,17 +110,14 @@ public final class DefaultSchemaResourceManager implements SchemaResourceManager
         // Attach a soft cache of IRSchemaSource instances. This is important during convergence when we are fishing
         // for a consistent set of modules, as it skips the need to re-parse the text sources multiple times. It also
         // helps establishing different sets of contexts, as they can share this pre-made cache.
-        repository.registerSchemaSourceListener(
-            new SoftSchemaSourceCache<>(repository, YangIRSource.class));
+        repository.registerSchemaSourceListener(new SoftSchemaSourceCache<>(repository, YangIRSource.class));
 
         // Attach the filesystem cache, providing persistence capability, so that restarts do not require us to
         // re-populate the cache. This also acts as a side-load capability, as anything pre-populated into that
         // directory will not be fetched from the device.
-        repository.registerSchemaSourceListener(new FilesystemSchemaSourceCache<>(repository,
-                YangTextSource.class, new File(rootDirectory + File.separator + subdir)));
+        repository.registerSchemaSourceListener(new FilesystemSchemaSourceCache<>(repository, YangTextSource.class,
+            new File(rootDirectory + File.separator + subdir)));
 
-        return new SchemaResourcesDTO(repository, repository,
-            repository.createEffectiveModelContextFactory(SchemaContextFactoryConfiguration.getDefault()),
-            new NetconfStateSchemasResolverImpl());
+        return new DefaultDeviceNetconfSchemaProvider(repository);
     }
 }
similarity index 97%
rename from plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/DeviceSources.java
rename to plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DeviceSources.java
index d7d2d54a80201c5101c1de98ce31e844216d4066..39de902c513ad8bb0dcee6a0422468b88a89b87d 100644 (file)
@@ -5,7 +5,7 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-package org.opendaylight.netconf.client.mdsal;
+package org.opendaylight.netconf.client.mdsal.impl;
 
 import static java.util.Objects.requireNonNull;
 
diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetup.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetup.java
new file mode 100644 (file)
index 0000000..d93460f
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.client.mdsal.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.opendaylight.netconf.api.CapabilityURN;
+import org.opendaylight.netconf.client.mdsal.NetconfDevice.EmptySchemaContextException;
+import org.opendaylight.netconf.client.mdsal.NetconfDeviceCapabilities;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema;
+import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
+import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.unavailable.capabilities.UnavailableCapability;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.unavailable.capabilities.UnavailableCapability.FailureReason;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
+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.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Schema builder that tries to build schema context from provided sources or biggest subset of it.
+ */
+final class SchemaSetup implements FutureCallback<EffectiveModelContext> {
+    private static final Logger LOG = LoggerFactory.getLogger(SchemaSetup.class);
+
+    private final SettableFuture<DeviceNetconfSchema> resultFuture = SettableFuture.create();
+    private final Set<AvailableCapability> nonModuleBasedCapabilities = new HashSet<>();
+    private final Map<QName, FailureReason> unresolvedCapabilites = new HashMap<>();
+    private final Set<AvailableCapability> resolvedCapabilities = new HashSet<>();
+
+    private final RemoteDeviceId deviceId;
+    private final DeviceSources deviceSources;
+    private final NetconfSessionPreferences remoteSessionCapabilities;
+    private final SchemaRepository repository;
+    private final EffectiveModelContextFactory contextFactory;
+
+    private Collection<SourceIdentifier> requiredSources;
+
+    SchemaSetup(final SchemaRepository repository, final EffectiveModelContextFactory contextFactory,
+            final RemoteDeviceId deviceId, final DeviceSources deviceSources,
+            final NetconfSessionPreferences remoteSessionCapabilities) {
+        this.repository = requireNonNull(repository);
+        this.contextFactory = requireNonNull(contextFactory);
+        this.deviceId = requireNonNull(deviceId);
+        this.deviceSources = requireNonNull(deviceSources);
+        this.remoteSessionCapabilities = requireNonNull(remoteSessionCapabilities);
+
+        // If device supports notifications and does not contain necessary modules, add them automatically
+        if (remoteSessionCapabilities.containsNonModuleCapability(CapabilityURN.NOTIFICATION)) {
+            // FIXME: mutable collection modification!
+            deviceSources.getRequiredSourcesQName().addAll(List.of(
+                org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
+                    .YangModuleInfoImpl.getInstance().getName(),
+                org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
+                    .YangModuleInfoImpl.getInstance().getName())
+            );
+        }
+
+        requiredSources = deviceSources.getRequiredSources();
+        final var missingSources = filterMissingSources(requiredSources);
+
+        addUnresolvedCapabilities(getQNameFromSourceIdentifiers(missingSources),
+            UnavailableCapability.FailureReason.MissingSource);
+        requiredSources.removeAll(missingSources);
+    }
+
+    ListenableFuture<DeviceNetconfSchema> startResolution() {
+        trySetupSchema();
+        return resultFuture;
+    }
+
+    @Override
+    public void onSuccess(final EffectiveModelContext result) {
+        LOG.debug("{}: Schema context built successfully from {}", deviceId, requiredSources);
+
+        final Collection<QName> filteredQNames = Sets.difference(deviceSources.getRequiredSourcesQName(),
+                unresolvedCapabilites.keySet());
+        resolvedCapabilities.addAll(filteredQNames.stream()
+            .map(capability -> new AvailableCapabilityBuilder()
+                .setCapability(capability.toString())
+                .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability))
+                .build())
+            .collect(Collectors.toList()));
+
+        nonModuleBasedCapabilities.addAll(remoteSessionCapabilities.nonModuleCaps().keySet().stream()
+            .map(capability -> new AvailableCapabilityBuilder()
+                .setCapability(capability)
+                .setCapabilityOrigin(remoteSessionCapabilities.capabilityOrigin(capability))
+                .build())
+            .collect(Collectors.toList()));
+
+
+        resultFuture.set(new DeviceNetconfSchema(new NetconfDeviceCapabilities(
+            ImmutableMap.copyOf(unresolvedCapabilites), ImmutableSet.copyOf(resolvedCapabilities),
+            ImmutableSet.copyOf(nonModuleBasedCapabilities)), result));
+    }
+
+    @Override
+    public void onFailure(final Throwable cause) {
+        // 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 (cause instanceof MissingSchemaSourceException) {
+            requiredSources = handleMissingSchemaSourceException((MissingSchemaSourceException) cause);
+        } else if (cause instanceof SchemaResolutionException) {
+            requiredSources = handleSchemaResolutionException((SchemaResolutionException) cause);
+        } else {
+            LOG.debug("Unhandled failure", cause);
+            resultFuture.setException(cause);
+            // No more trying...
+            return;
+        }
+
+        trySetupSchema();
+    }
+
+    private void trySetupSchema() {
+        if (!requiredSources.isEmpty()) {
+            // Initiate async resolution, drive it back based on the result
+            LOG.trace("{}: Trying to build schema context from {}", deviceId, requiredSources);
+            Futures.addCallback(contextFactory.createEffectiveModelContext(requiredSources), this,
+                MoreExecutors.directExecutor());
+        } else {
+            LOG.debug("{}: no more sources for schema context", deviceId);
+            resultFuture.setException(
+                new EmptySchemaContextException(deviceId + ": No more sources for schema context"));
+        }
+    }
+
+    private List<SourceIdentifier> filterMissingSources(final Collection<SourceIdentifier> origSources) {
+        return origSources.parallelStream()
+            .filter(sourceId -> {
+                try {
+                    repository.getSchemaSource(sourceId, YangTextSource.class).get();
+                    return false;
+                } catch (InterruptedException | ExecutionException e) {
+                    LOG.debug("Failed to acquire source {}", sourceId, e);
+                    return true;
+                }
+            })
+            .collect(Collectors.toList());
+    }
+
+    private void addUnresolvedCapabilities(final Collection<QName> capabilities, final FailureReason reason) {
+        for (QName s : capabilities) {
+            unresolvedCapabilites.put(s, reason);
+        }
+    }
+
+    private List<SourceIdentifier> handleMissingSchemaSourceException(
+            final MissingSchemaSourceException exception) {
+        // In case source missing, try without it
+        final SourceIdentifier missingSource = exception.sourceId();
+        LOG.warn("{}: Unable to build schema context, missing source {}, will reattempt without it",
+            deviceId, missingSource);
+        LOG.debug("{}: Unable to build schema context, missing source {}, will reattempt without it",
+            deviceId, missingSource, exception);
+        final var qNameOfMissingSource = getQNameFromSourceIdentifiers(Sets.newHashSet(missingSource));
+        if (!qNameOfMissingSource.isEmpty()) {
+            addUnresolvedCapabilities(qNameOfMissingSource, UnavailableCapability.FailureReason.MissingSource);
+        }
+        return stripUnavailableSource(missingSource);
+    }
+
+    private Collection<SourceIdentifier> handleSchemaResolutionException(
+            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?
+        final var failedSourceId = resolutionException.sourceId();
+        if (failedSourceId != null) {
+            // flawed model - exclude it
+            LOG.warn("{}: Unable to build schema context, failed to resolve source {}, will reattempt without it",
+                deviceId, failedSourceId);
+            LOG.warn("{}: Unable to build schema context, failed to resolve source {}, will reattempt without it",
+                deviceId, failedSourceId, resolutionException);
+            addUnresolvedCapabilities(getQNameFromSourceIdentifiers(List.of(failedSourceId)),
+                    UnavailableCapability.FailureReason.UnableToResolve);
+            return stripUnavailableSource(failedSourceId);
+        }
+        // unsatisfied imports
+        addUnresolvedCapabilities(
+            getQNameFromSourceIdentifiers(resolutionException.getUnsatisfiedImports().keySet()),
+            UnavailableCapability.FailureReason.UnableToResolve);
+        LOG.warn("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
+            deviceId, resolutionException.getUnsatisfiedImports());
+        LOG.debug("{}: Unable to build schema context, unsatisfied imports {}, will reattempt with resolved only",
+            deviceId, resolutionException.getUnsatisfiedImports(), resolutionException);
+        return resolutionException.getResolvedSources();
+    }
+
+    private List<SourceIdentifier> stripUnavailableSource(final SourceIdentifier sourceIdToRemove) {
+        final var tmp = new ArrayList<>(requiredSources);
+        checkState(tmp.remove(sourceIdToRemove), "%s: Trying to remove %s from %s failed", deviceId, sourceIdToRemove,
+            requiredSources);
+        return tmp;
+    }
+
+    private Collection<QName> getQNameFromSourceIdentifiers(final Collection<SourceIdentifier> identifiers) {
+        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 : {}", deviceId,
+                    identifiers);
+        }
+        return Collections2.filter(qNames, Predicates.notNull());
+    }
+
+    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.name().getLocalName())) {
+                continue;
+            }
+
+            if (Objects.equals(identifier.revision(), qname.getRevision().orElse(null))) {
+                return qname;
+            }
+        }
+        LOG.warn("Unable to map identifier to a devices reported capability: {} Available: {}",identifier,
+                deviceSources.getRequiredSourcesQName());
+        // return null since we cannot find the QName,
+        // this capability will be removed from required sources and not reported as unresolved-capability
+        return null;
+    }
+}
index c67c344d7561c55bf97ca35cb9f5452b7948df97..85484243b460b58e9cc5f2d09d128541649e561d 100644 (file)
@@ -7,23 +7,11 @@
  */
 package org.opendaylight.netconf.client.mdsal;
 
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.impl.DefaultBaseNetconfSchemaProvider;
-import org.opendaylight.yangtools.yang.parser.api.YangParserException;
 import org.opendaylight.yangtools.yang.parser.impl.DefaultYangParserFactory;
 
 public abstract class AbstractBaseSchemasTest {
-    protected static BaseNetconfSchemaProvider BASE_SCHEMAS;
-
-    @BeforeClass
-    public static void initBaseSchemas() throws YangParserException {
-        BASE_SCHEMAS = new DefaultBaseNetconfSchemaProvider(new DefaultYangParserFactory());
-    }
-
-    @AfterClass
-    public static void freeBaseSchemas() {
-        BASE_SCHEMAS = null;
-    }
+    protected static final BaseNetconfSchemaProvider BASE_SCHEMAS =
+        new DefaultBaseNetconfSchemaProvider(new DefaultYangParserFactory());
 }
index c61f76b7dd1bf2ea9179f6b2112738199ebc4fd2..87f843e2079f980aa437c3fe723f56938a8037e8 100644 (file)
@@ -7,21 +7,11 @@
  */
 package org.opendaylight.netconf.client.mdsal;
 
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
 
 public abstract class AbstractTestModelTest extends AbstractBaseSchemasTest {
-    protected static EffectiveModelContext SCHEMA_CONTEXT;
-
-    @BeforeClass
-    public static final void setupSchemaContext() {
-        SCHEMA_CONTEXT = YangParserTestUtils.parseYangResource("/schemas/test-module.yang");
-    }
-
-    @AfterClass
-    public static final void tearDownSchemaContext() {
-        SCHEMA_CONTEXT = null;
-    }
+    protected static final @NonNull EffectiveModelContext SCHEMA_CONTEXT =
+        YangParserTestUtils.parseYangResource("/schemas/test-module.yang");
 }
index 966e79fe0066dcaef40e081c933e6563370c9f09..823266c913e5fb89ee81eba87caf0f60bdca3d3e 100644 (file)
@@ -12,7 +12,6 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
-import com.google.common.util.concurrent.FluentFuture;
 import java.net.InetSocketAddress;
 import java.util.Optional;
 import java.util.Set;
@@ -23,9 +22,8 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
-import org.opendaylight.mdsal.dom.api.DOMRpcResult;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.GetSchema;
 import org.opendaylight.yangtools.util.concurrent.FluentFutures;
@@ -42,18 +40,17 @@ import org.w3c.dom.Element;
 @RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class MonitoringSchemaSourceProviderTest {
     @Mock
-    private DOMRpcService service;
+    private NetconfRpcService service;
 
     private MonitoringSchemaSourceProvider provider;
 
     @Before
     public void setUp() throws Exception {
-        final DOMRpcResult value = new DefaultDOMRpcResult(getNode(), Set.of());
-        final FluentFuture<DOMRpcResult> response = FluentFutures.immediateFluentFuture(value);
-        doReturn(response).when(service).invokeRpc(any(QName.class), any(ContainerNode.class));
+        doReturn(FluentFutures.immediateFluentFuture(new DefaultDOMRpcResult(getNode(), Set.of()))).when(service)
+            .invokeNetconf(any(), any());
 
         provider = new MonitoringSchemaSourceProvider(
-                new RemoteDeviceId("device1", InetSocketAddress.createUnresolved("localhost", 17830)), service);
+            new RemoteDeviceId("device1", InetSocketAddress.createUnresolved("localhost", 17830)), service);
     }
 
     @Test
@@ -61,7 +58,7 @@ public class MonitoringSchemaSourceProviderTest {
         final SourceIdentifier identifier = new SourceIdentifier("test", "2016-02-08");
         final YangTextSource source = provider.getSource(identifier).get();
         assertEquals(identifier, source.sourceId());
-        verify(service).invokeRpc(GetSchema.QNAME,
+        verify(service).invokeNetconf(GetSchema.QNAME,
                 MonitoringSchemaSourceProvider.createGetSchemaRequest("test", Optional.of("2016-02-08")));
     }
 
index b2f8a97678317b514a65f4e21d811bbde2003a78..8044aee8b094fca46e5e6df7add38a843e74a5ac 100644 (file)
@@ -14,7 +14,6 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyCollection;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -28,7 +27,6 @@ import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.SettableFuture;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -44,21 +42,22 @@ import org.opendaylight.netconf.api.CapabilityURN;
 import org.opendaylight.netconf.api.messages.NetconfMessage;
 import org.opendaylight.netconf.api.xml.XmlUtil;
 import org.opendaylight.netconf.client.mdsal.NetconfDevice.EmptySchemaContextException;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchema;
+import org.opendaylight.netconf.client.mdsal.api.DeviceNetconfSchemaProvider;
 import org.opendaylight.netconf.client.mdsal.api.NetconfDeviceSchemasResolver;
 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
+import org.opendaylight.netconf.client.mdsal.impl.DefaultDeviceNetconfSchemaProvider;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.NetconfState;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
-import org.opendaylight.yangtools.concepts.Registration;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
 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.SchemaRepository;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
@@ -83,6 +82,8 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
 
     @Mock
     private SchemaSourceRegistry schemaRegistry;
+    @Mock
+    private DeviceNetconfSchemaProvider schemaProvider;
 
     @BeforeClass
     public static final void setupNotification() throws Exception {
@@ -90,81 +91,31 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
             NetconfDeviceTest.class.getResourceAsStream("/notification-payload.xml")));
     }
 
-    @Test
-    public void testNetconfDeviceFlawedModelFailedResolution() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
-
-        final EffectiveModelContextFactory schemaFactory = getSchemaFactory();
-        final SchemaRepository schemaRepository = getSchemaRepository();
-
-        final SchemaResolutionException schemaResolutionException =
-                new SchemaResolutionException("fail first", TEST_SID, new Throwable("YangTools parser fail"));
-        doAnswer(invocation -> {
-            if (invocation.getArgument(0, Collection.class).size() == 2) {
-                return Futures.immediateFailedFuture(schemaResolutionException);
-            } else {
-                return Futures.immediateFuture(SCHEMA_CONTEXT);
-            }
-        }).when(schemaFactory).createEffectiveModelContext(anyCollection());
-
-        final NetconfDeviceSchemasResolver stateSchemasResolver =
-            (deviceRpc, remoteSessionCapabilities, id, schemaContext) -> {
-                final var first = SCHEMA_CONTEXT.getModules().iterator().next();
-                final var qName = QName.create(first.getQNameModule(), first.getName());
-                return Futures.immediateFuture(new NetconfStateSchemas(
-                    Set.of(qName, QName.create(first.getQNameModule(), "test-module2"))));
-            };
-
-        doReturn(mock(Registration.class)).when(schemaRegistry).registerSchemaSource(any(), any());
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice
-                .SchemaResourcesDTO(schemaRegistry, schemaRepository, schemaFactory, stateSchemasResolver);
-
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setReconnectOnSchemasChange(true)
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setId(getId())
-                .setSalFacade(facade)
-                .setBaseSchemas(BASE_SCHEMAS)
-                .build();
-        // Monitoring supported
-        final NetconfSessionPreferences sessionCaps = getSessionCaps(true, List.of(TEST_CAPABILITY, TEST_CAPABILITY2));
-        device.onRemoteSessionUp(sessionCaps, listener);
-
-        verify(facade, timeout(5000)).onDeviceConnected(any(NetconfDeviceSchema.class),
-            any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
-        verify(schemaFactory, times(2)).createEffectiveModelContext(anyCollection());
-    }
-
     @Test
     public void testNetconfDeviceFailFirstSchemaFailSecondEmpty() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
+        final var facade = getFacade();
+        final var listener = getListener();
 
-        final EffectiveModelContextFactory schemaFactory = getSchemaFactory();
-        final SchemaRepository schemaRepository = getSchemaRepository();
+        final var schemaFactory = getSchemaFactory();
 
         // Make fallback attempt to fail due to empty resolved sources
-        final SchemaResolutionException schemaResolutionException = new SchemaResolutionException("fail first",
+        final var schemaResolutionException = new SchemaResolutionException("fail first",
             new SourceIdentifier("test-module", "2013-07-22"), new Throwable());
         doReturn(Futures.immediateFailedFuture(schemaResolutionException))
                 .when(schemaFactory).createEffectiveModelContext(anyCollection());
 
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice
-                .SchemaResourcesDTO(schemaRegistry, schemaRepository, schemaFactory, STATE_SCHEMAS_RESOLVER);
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setReconnectOnSchemasChange(true)
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setId(getId())
-                .setSalFacade(facade)
-                .setBaseSchemas(BASE_SCHEMAS)
-                .build();
+        final var device = new NetconfDeviceBuilder()
+            .setReconnectOnSchemasChange(true)
+            .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(), schemaFactory,
+                STATE_SCHEMAS_RESOLVER))
+            .setProcessingExecutor(MoreExecutors.directExecutor())
+            .setId(getId())
+            .setSalFacade(facade)
+            .setBaseSchemaProvider(BASE_SCHEMAS)
+            .build();
 
         // Monitoring not supported
-        final NetconfSessionPreferences sessionCaps = getSessionCaps(false, List.of(TEST_CAPABILITY));
-        device.onRemoteSessionUp(sessionCaps, listener);
+        device.onRemoteSessionUp(getSessionCaps(false, TEST_CAPABILITY), listener);
 
         final var captor = ArgumentCaptor.forClass(Throwable.class);
         verify(facade, timeout(5000)).onDeviceFailed(captor.capture());
@@ -174,60 +125,9 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
         verify(schemaFactory, times(1)).createEffectiveModelContext(anyCollection());
     }
 
-    @Test
-    public void testNetconfDeviceMissingSource() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
-
-        final EffectiveModelContextFactory schemaFactory = getSchemaFactory();
-        final SchemaRepository schemaRepository = getSchemaRepository();
-
-        // Make fallback attempt to fail due to empty resolved sources
-        final MissingSchemaSourceException schemaResolutionException =
-                new MissingSchemaSourceException(TEST_SID, "fail first");
-        doReturn(Futures.immediateFailedFuture(schemaResolutionException))
-                .when(schemaRepository).getSchemaSource(eq(TEST_SID), eq(YangTextSource.class));
-        doAnswer(invocation -> {
-            if (invocation.getArgument(0, Collection.class).size() == 2) {
-                return Futures.immediateFailedFuture(schemaResolutionException);
-            } else {
-                return Futures.immediateFuture(SCHEMA_CONTEXT);
-            }
-        }).when(schemaFactory).createEffectiveModelContext(anyCollection());
-
-        final NetconfDeviceSchemasResolver stateSchemasResolver =
-            (deviceRpc, remoteSessionCapabilities, id, schemaContext) -> {
-                final var first = SCHEMA_CONTEXT.getModules().iterator().next();
-                final var qName = QName.create(first.getQNameModule(), first.getName());
-                return Futures.immediateFuture(new NetconfStateSchemas(
-                    Set.of(qName, QName.create(first.getQNameModule(), "test-module2"))));
-            };
-
-        doReturn(mock(Registration.class)).when(schemaRegistry).registerSchemaSource(any(), any());
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice
-                .SchemaResourcesDTO(schemaRegistry, schemaRepository, schemaFactory, stateSchemasResolver);
-
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setReconnectOnSchemasChange(true)
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setBaseSchemas(BASE_SCHEMAS)
-                .setId(getId())
-                .setSalFacade(facade)
-                .build();
-        // Monitoring supported
-        final NetconfSessionPreferences sessionCaps =
-                getSessionCaps(true, List.of(TEST_CAPABILITY, TEST_CAPABILITY2));
-        device.onRemoteSessionUp(sessionCaps, listener);
-
-        verify(facade, timeout(5000)).onDeviceConnected(any(NetconfDeviceSchema.class),
-            any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
-        verify(schemaFactory, times(1)).createEffectiveModelContext(anyCollection());
-    }
-
     private static SchemaRepository getSchemaRepository() {
-        final SchemaRepository mock = mock(SchemaRepository.class);
-        final YangTextSource mockRep = mock(YangTextSource.class);
+        final var mock = mock(SchemaRepository.class);
+        final var mockRep = mock(YangTextSource.class);
         doReturn(Futures.immediateFuture(mockRep))
                 .when(mock).getSchemaSource(any(SourceIdentifier.class), eq(YangTextSource.class));
         return mock;
@@ -235,59 +135,56 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
 
     @Test
     public void testNotificationBeforeSchema() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
-        final EffectiveModelContextFactory schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
-        final SettableFuture<SchemaContext> schemaFuture = SettableFuture.create();
-        doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection());
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
-            getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setReconnectOnSchemasChange(true)
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setId(getId())
-                .setSalFacade(facade)
-                .setBaseSchemas(BASE_SCHEMAS)
-                .build();
-
-        final NetconfSessionPreferences sessionCaps = getSessionCaps(true, List.of(TEST_CAPABILITY));
-        device.onRemoteSessionUp(sessionCaps, listener);
+        final var facade = getFacade();
+        final var deviceSchemaProvider = mock(DeviceNetconfSchemaProvider.class);
+        final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
+        doReturn(schemaFuture).when(deviceSchemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
+
+        final var device = new NetconfDeviceBuilder()
+            .setReconnectOnSchemasChange(true)
+            .setDeviceSchemaProvider(deviceSchemaProvider)
+            .setProcessingExecutor(MoreExecutors.directExecutor())
+            .setId(getId())
+            .setSalFacade(facade)
+            .setBaseSchemaProvider(BASE_SCHEMAS)
+            .build();
+
+        final var sessionCaps = getSessionCaps(true, TEST_CAPABILITY);
+        device.onRemoteSessionUp(sessionCaps, getListener());
 
         device.onNotification(NOTIFICATION);
         device.onNotification(NOTIFICATION);
         verify(facade, times(0)).onNotification(any(DOMNotification.class));
 
-        verify(facade, times(0)).onNotification(any(DOMNotification.class));
-        schemaFuture.set(NetconfToNotificationTest.getNotificationSchemaContext(getClass(), false));
+        // Now enable schema
+        schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(),
+            NetconfToNotificationTest.getNotificationSchemaContext(NetconfDeviceTest.class, false)));
+
         verify(facade, timeout(10000).times(2)).onNotification(any(DOMNotification.class));
 
         device.onNotification(NOTIFICATION);
-        verify(facade, timeout(10000).times(3)).onNotification(any(DOMNotification.class));
+        verify(facade, times(3)).onNotification(any(DOMNotification.class));
     }
 
     @Test
     public void testNetconfDeviceReconnect() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
+        final var facade = getFacade();
+        final var listener = getListener();
+
+        final var deviceSchemaProvider = mockDeviceNetconfSchemaProvider();
 
-        final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(
-                schemaRegistry, getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setReconnectOnSchemasChange(true)
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setId(getId())
-                .setSalFacade(facade)
-                .setBaseSchemas(BASE_SCHEMAS)
-                .build();
-        final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
-                List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION));
+        final var device = new NetconfDeviceBuilder()
+            .setReconnectOnSchemasChange(true)
+            .setDeviceSchemaProvider(deviceSchemaProvider)
+            .setProcessingExecutor(MoreExecutors.directExecutor())
+            .setId(getId())
+            .setSalFacade(facade)
+            .setBaseSchemaProvider(BASE_SCHEMAS)
+            .build();
+        final var sessionCaps = getSessionCaps(true,
+                TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION);
         device.onRemoteSessionUp(sessionCaps, listener);
 
-        verify(schemaContextProviderFactory, timeout(5000)).createEffectiveModelContext(anyCollection());
         verify(facade, timeout(5000)).onDeviceConnected(
                 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
 
@@ -296,61 +193,55 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
 
         device.onRemoteSessionUp(sessionCaps, listener);
 
-        verify(schemaContextProviderFactory, timeout(5000).times(2)).createEffectiveModelContext(anyCollection());
         verify(facade, timeout(5000).times(2)).onDeviceConnected(
                 any(NetconfDeviceSchema.class), any(NetconfSessionPreferences.class), any(RemoteDeviceServices.class));
     }
 
     @Test
     public void testNetconfDeviceDisconnectListenerCallCancellation() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
-        final EffectiveModelContextFactory schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
-        final SettableFuture<SchemaContext> schemaFuture = SettableFuture.create();
-        doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection());
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
-            getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setReconnectOnSchemasChange(true)
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setId(getId())
-                .setSalFacade(facade)
-                .setBaseSchemas(BASE_SCHEMAS)
-                .build();
-        final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
-                List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION));
+        final var facade = getFacade();
+        final var schemaFuture = SettableFuture.<DeviceNetconfSchema>create();
+        doReturn(schemaFuture).when(schemaProvider).deviceNetconfSchemaFor(any(), any(), any(), any(), any());
+
+        final var device = new NetconfDeviceBuilder()
+            .setReconnectOnSchemasChange(true)
+            .setDeviceSchemaProvider(schemaProvider)
+            .setProcessingExecutor(MoreExecutors.directExecutor())
+            .setId(getId())
+            .setSalFacade(facade)
+            .setBaseSchemaProvider(BASE_SCHEMAS)
+            .build();
         //session up, start schema resolution
-        device.onRemoteSessionUp(sessionCaps, listener);
+        device.onRemoteSessionUp(getSessionCaps(true,
+            TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION), getListener());
         //session closed
         device.onRemoteSessionDown();
         verify(facade, timeout(5000)).onDeviceDisconnected();
         //complete schema setup
-        schemaFuture.set(SCHEMA_CONTEXT);
+        schemaFuture.set(new DeviceNetconfSchema(NetconfDeviceCapabilities.empty(), SCHEMA_CONTEXT));
         //facade.onDeviceDisconnected() was called, so facade.onDeviceConnected() shouldn't be called anymore
         verify(facade, after(1000).never()).onDeviceConnected(any(), any(), any(RemoteDeviceServices.class));
     }
 
     @Test
     public void testNetconfDeviceReconnectBeforeSchemaSetup() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
+        final var facade = getFacade();
 
-        final EffectiveModelContextFactory schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
-        final SettableFuture<SchemaContext> schemaFuture = SettableFuture.create();
+        final var schemaContextProviderFactory = mock(EffectiveModelContextFactory.class);
+        final var schemaFuture = SettableFuture.<EffectiveModelContext>create();
         doReturn(schemaFuture).when(schemaContextProviderFactory).createEffectiveModelContext(anyCollection());
 
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(
-            schemaRegistry, getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
-        final NetconfDevice device = new NetconfDeviceBuilder()
+        final var device = new NetconfDeviceBuilder()
             .setReconnectOnSchemasChange(true)
-            .setSchemaResourcesDTO(schemaResourcesDTO)
-            .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
+            .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider(getSchemaRepository(),
+                schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER))
+            .setProcessingExecutor(MoreExecutors.directExecutor())
             .setId(getId())
             .setSalFacade(facade)
-            .setBaseSchemas(BASE_SCHEMAS)
+            .setBaseSchemaProvider(BASE_SCHEMAS)
             .build();
-        final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
-            List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION));
+        final var sessionCaps = getSessionCaps(true,
+            TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION);
 
         final NetconfDeviceCommunicator listener = getListener();
         // session up, start schema resolution
@@ -371,29 +262,23 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
 
     @Test
     public void testNetconfDeviceAvailableCapabilitiesBuilding() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
+        final var facade = getFacade();
+        final var listener = getListener();
+
+        final var netconfSpy = spy(new NetconfDeviceBuilder()
+            .setReconnectOnSchemasChange(true)
+            .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
+            .setProcessingExecutor(MoreExecutors.directExecutor())
+            .setId(getId())
+            .setSalFacade(facade)
+            .setBaseSchemaProvider(BASE_SCHEMAS)
+            .build());
 
-        final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
-            getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setReconnectOnSchemasChange(true)
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setId(getId())
-                .setSalFacade(facade)
-                .setBaseSchemas(BASE_SCHEMAS)
-                .build();
-        final NetconfDevice netconfSpy = spy(device);
-
-        final NetconfSessionPreferences sessionCaps = getSessionCaps(true,
-                List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION));
-        final Map<QName, CapabilityOrigin> moduleBasedCaps = new HashMap<>();
+        final var sessionCaps = getSessionCaps(true,
+            TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION);
+        final var moduleBasedCaps = new HashMap<QName, CapabilityOrigin>();
         moduleBasedCaps.putAll(sessionCaps.moduleBasedCaps());
-        moduleBasedCaps
-                .put(QName.create("(test:qname:side:loading)test"), CapabilityOrigin.UserDefined);
+        moduleBasedCaps.put(QName.create("(test:qname:side:loading)test"), CapabilityOrigin.UserDefined);
 
         netconfSpy.onRemoteSessionUp(sessionCaps.replaceModuleCaps(moduleBasedCaps), listener);
 
@@ -414,22 +299,16 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
 
     @Test
     public void testNetconfDeviceNotificationsModelNotPresentWithCapability() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
-        final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
-            getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setId(getId())
-                .setSalFacade(facade)
-                .setBaseSchemas(BASE_SCHEMAS)
-                .build();
-        final NetconfDevice netconfSpy = spy(device);
+        final var facade = getFacade();
+        final var netconfSpy = spy(new NetconfDeviceBuilder()
+            .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
+            .setProcessingExecutor(MoreExecutors.directExecutor())
+            .setId(getId())
+            .setSalFacade(facade)
+            .setBaseSchemaProvider(BASE_SCHEMAS)
+            .build());
 
-        netconfSpy.onRemoteSessionUp(getSessionCaps(false, List.of(CapabilityURN.NOTIFICATION)), listener);
+        netconfSpy.onRemoteSessionUp(getSessionCaps(false, CapabilityURN.NOTIFICATION), getListener());
 
         final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
         verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
@@ -445,55 +324,19 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
     }
 
     @Test
-    public void testNetconfDeviceNotificationsCapabilityIsNotPresent() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
-        final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
-            getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setId(getId())
-                .setSalFacade(facade)
-                .setBaseSchemas(BASE_SCHEMAS)
-                .build();
-        final NetconfDevice netconfSpy = spy(device);
-
-        final NetconfSessionPreferences sessionCaps = getSessionCaps(false,
-                List.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION));
-
-        netconfSpy.onRemoteSessionUp(sessionCaps, listener);
-
-        final var argument = ArgumentCaptor.forClass(NetconfDeviceSchema.class);
-        verify(facade, timeout(5000)).onDeviceConnected(argument.capture(), any(NetconfSessionPreferences.class),
-                any(RemoteDeviceServices.class));
+    public void testNetconfDeviceNotificationsModelIsPresent() throws Exception {
+        final var facade = getFacade();
+        final var listener = getListener();
 
-        assertEquals(Set.of(new AvailableCapabilityBuilder()
-            .setCapability("(test:namespace?revision=2013-07-22)test-module")
-            .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
-            .build()), argument.getValue().capabilities().resolvedCapabilities());
-    }
+        final var netconfSpy = spy(new NetconfDeviceBuilder()
+            .setDeviceSchemaProvider(mockDeviceNetconfSchemaProvider())
+            .setProcessingExecutor(MoreExecutors.directExecutor())
+            .setId(getId())
+            .setSalFacade(facade)
+            .setBaseSchemaProvider(BASE_SCHEMAS)
+            .build());
 
-    @Test
-    public void testNetconfDeviceNotificationsModelIsPresent() throws Exception {
-        final RemoteDeviceHandler facade = getFacade();
-        final NetconfDeviceCommunicator listener = getListener();
-        final EffectiveModelContextFactory schemaContextProviderFactory = getSchemaFactory();
-
-        final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry,
-            getSchemaRepository(), schemaContextProviderFactory, STATE_SCHEMAS_RESOLVER);
-        final NetconfDevice device = new NetconfDeviceBuilder()
-                .setSchemaResourcesDTO(schemaResourcesDTO)
-                .setGlobalProcessingExecutor(MoreExecutors.directExecutor())
-                .setId(getId())
-                .setSalFacade(facade)
-                .setBaseSchemas(BASE_SCHEMAS)
-                .build();
-        final NetconfDevice netconfSpy = spy(device);
-
-        netconfSpy.onRemoteSessionUp(getSessionCaps(false, List.of()).replaceModuleCaps(Map.of(
+        netconfSpy.onRemoteSessionUp(getSessionCaps(false).replaceModuleCaps(Map.of(
             org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.netconf.notification._1._0.rev080714
                 .YangModuleInfoImpl.getInstance().getName(), CapabilityOrigin.DeviceAdvertised,
             org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715
@@ -515,8 +358,8 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
                 .build()), argument.getValue().capabilities().resolvedCapabilities());
     }
 
-    private static EffectiveModelContextFactory getSchemaFactory() throws Exception {
-        final EffectiveModelContextFactory schemaFactory = mock(EffectiveModelContextFactory.class);
+    private static EffectiveModelContextFactory getSchemaFactory() {
+        final var schemaFactory = mock(EffectiveModelContextFactory.class);
         doReturn(Futures.immediateFuture(SCHEMA_CONTEXT))
                 .when(schemaFactory).createEffectiveModelContext(anyCollection());
         return schemaFactory;
@@ -538,19 +381,29 @@ public class NetconfDeviceTest extends AbstractTestModelTest {
         return mock;
     }
 
+    private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider() {
+        return mockDeviceNetconfSchemaProvider(getSchemaRepository(), getSchemaFactory(), STATE_SCHEMAS_RESOLVER);
+    }
+
+    private DeviceNetconfSchemaProvider mockDeviceNetconfSchemaProvider(final SchemaRepository schemaRepository,
+            final EffectiveModelContextFactory schemaFactory, final NetconfDeviceSchemasResolver stateSchemasResolver) {
+        return new DefaultDeviceNetconfSchemaProvider(schemaRegistry, schemaRepository, schemaFactory,
+            stateSchemasResolver);
+    }
+
     public RemoteDeviceId getId() {
         return new RemoteDeviceId("test-D", InetSocketAddress.createUnresolved("localhost", 22));
     }
 
     public NetconfSessionPreferences getSessionCaps(final boolean addMonitor,
-                                                    final Collection<String> additionalCapabilities) {
+            final String... additionalCapabilities) {
         final var capabilities = new ArrayList<String>();
         capabilities.add(CapabilityURN.BASE);
         capabilities.add(CapabilityURN.BASE_1_1);
         if (addMonitor) {
             capabilities.add(NetconfState.QNAME.getNamespace().toString());
         }
-        capabilities.addAll(additionalCapabilities);
+        capabilities.addAll(List.of(additionalCapabilities));
         return NetconfSessionPreferences.fromStrings(capabilities);
     }
 
index 5dfebde7919a77bcecf780716926aee7c0b88155..4fb75588b01c48588cda090809266614b44765bc 100644 (file)
@@ -30,8 +30,8 @@ import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
-import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.netconf.client.mdsal.api.NetconfRpcService;
 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil;
@@ -64,7 +64,7 @@ public class NetconfStateSchemasTest extends AbstractBaseSchemasTest {
     private static ContainerNode SCHEMAS_PAYLOAD;
 
     @Mock
-    private DOMRpcService rpc;
+    private NetconfRpcService rpc;
 
     @BeforeClass
     public static void setUp() throws Exception {
@@ -104,7 +104,8 @@ public class NetconfStateSchemasTest extends AbstractBaseSchemasTest {
                         .build())
                     .build())
                 .build();
-        doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcReply))).when(rpc).invokeRpc(eq(Get.QNAME), any());
+        doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcReply))).when(rpc)
+            .invokeNetconf(eq(Get.QNAME), any());
         final var stateSchemas = assertSchemas(CAPS);
         final var availableYangSchemasQNames = stateSchemas.getAvailableYangSchemasQNames();
         assertEquals(69, availableYangSchemasQNames.size());
@@ -122,14 +123,15 @@ public class NetconfStateSchemasTest extends AbstractBaseSchemasTest {
     @Test
     public void testCreateFail() throws Exception {
         final var domEx = new DOMRpcImplementationNotAvailableException("not available");
-        doReturn(Futures.immediateFailedFuture(domEx)).when(rpc).invokeRpc(eq(Get.QNAME), any());
+        doReturn(Futures.immediateFailedFuture(domEx)).when(rpc).invokeNetconf(eq(Get.QNAME), any());
         assertSame(domEx, assertSchemasFailure());
     }
 
     @Test
     public void testCreateRpcError() throws Exception {
         final var rpcError = RpcResultBuilder.newError(ErrorType.RPC, new ErrorTag("fail"), "fail");
-        doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcError))).when(rpc).invokeRpc(eq(Get.QNAME), any());
+        doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(rpcError))).when(rpc)
+            .invokeNetconf(eq(Get.QNAME), any());
 
         final var ex = assertInstanceOf(OperationFailedException.class, assertSchemasFailure());
         assertEquals(List.of(rpcError), ex.getErrorList());
diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetupTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetupTest.java
new file mode 100644 (file)
index 0000000..d2e1be0
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.client.mdsal.impl;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyCollection;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.util.concurrent.Futures;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.netconf.client.mdsal.AbstractTestModelTest;
+import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
+import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapability.CapabilityOrigin;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.oper.available.capabilities.AvailableCapabilityBuilder;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.source.YangTextSource;
+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.SchemaRepository;
+import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
+import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider;
+
+@ExtendWith(MockitoExtension.class)
+class SchemaSetupTest extends AbstractTestModelTest {
+    private static final RemoteDeviceId DEVICE_ID = new RemoteDeviceId("someDevice", new InetSocketAddress(42));
+    private static final String TEST_NAMESPACE = "test:namespace";
+    private static final String TEST_MODULE = "test-module";
+    private static final String TEST_REVISION = "2013-07-22";
+    private static final SourceIdentifier TEST_SID = new SourceIdentifier(TEST_MODULE, TEST_REVISION);
+    private static final SourceIdentifier TEST_SID2 = new SourceIdentifier(TEST_MODULE + "2", TEST_REVISION);
+    private static final QName TEST_QNAME = QName.create(TEST_NAMESPACE, TEST_REVISION, TEST_MODULE);
+    private static final QName TEST_QNAME2 = QName.create(TEST_NAMESPACE, TEST_REVISION, TEST_MODULE + "2");
+
+    @Mock
+    private EffectiveModelContextFactory contextFactory;
+    @Mock
+    private SchemaRepository schemaRepository;
+    @Mock
+    private SchemaSourceProvider<YangTextSource> sourceProvider;
+    @Mock
+    private YangTextSource source;
+
+    @Test
+    void testNetconfDeviceFlawedModelFailedResolution() throws Exception {
+        final var ex = new SchemaResolutionException("fail first", TEST_SID, new Throwable("YangTools parser fail"));
+        doAnswer(invocation -> invocation.getArgument(0, Collection.class).size() == 2
+            ? Futures.immediateFailedFuture(ex) : Futures.immediateFuture(SCHEMA_CONTEXT))
+            .when(contextFactory).createEffectiveModelContext(anyCollection());
+
+        doReturn(Futures.immediateFuture(source)).when(schemaRepository)
+            .getSchemaSource(any(), eq(YangTextSource.class));
+
+        final var setup = new SchemaSetup(schemaRepository, contextFactory, DEVICE_ID,
+            new DeviceSources(Set.of(TEST_QNAME, TEST_QNAME2), Set.of(TEST_QNAME, TEST_QNAME2), sourceProvider),
+            NetconfSessionPreferences.fromStrings(Set.of()));
+
+        final var result = Futures.getDone(setup.startResolution());
+        verify(contextFactory, times(2)).createEffectiveModelContext(anyCollection());
+        assertSame(SCHEMA_CONTEXT, result.modelContext());
+    }
+
+    @Test
+    void testNetconfDeviceMissingSource() throws Exception {
+        // Make fallback attempt to fail due to empty resolved sources
+        final var ex = new MissingSchemaSourceException(TEST_SID, "fail first");
+        doReturn(Futures.immediateFailedFuture(ex))
+                .when(schemaRepository).getSchemaSource(TEST_SID, YangTextSource.class);
+        doReturn(Futures.immediateFuture(source)).when(schemaRepository)
+            .getSchemaSource(TEST_SID2, YangTextSource.class);
+        doReturn(Futures.immediateFuture(SCHEMA_CONTEXT)).when(contextFactory)
+            .createEffectiveModelContext(anyCollection());
+
+        final var setup = new SchemaSetup(schemaRepository, contextFactory, DEVICE_ID,
+            new DeviceSources(Set.of(TEST_QNAME, TEST_QNAME2), Set.of(TEST_QNAME, TEST_QNAME2), sourceProvider),
+            NetconfSessionPreferences.fromStrings(Set.of()));
+
+        final var result = Futures.getDone(setup.startResolution());
+        final var captor = ArgumentCaptor.forClass(Collection.class);
+        verify(contextFactory).createEffectiveModelContext(captor.capture());
+        assertEquals(List.of(TEST_SID2), captor.getValue());
+        assertSame(SCHEMA_CONTEXT, result.modelContext());
+    }
+
+    @Test
+    void testNetconfDeviceNotificationsCapabilityIsNotPresent() throws Exception {
+        doReturn(Futures.immediateFuture(source)).when(schemaRepository)
+            .getSchemaSource(any(), eq(YangTextSource.class));
+        doReturn(Futures.immediateFuture(SCHEMA_CONTEXT)).when(contextFactory)
+            .createEffectiveModelContext(anyCollection());
+
+        final var setup = new SchemaSetup(schemaRepository, contextFactory, DEVICE_ID,
+            new DeviceSources(Set.of(TEST_QNAME), Set.of(TEST_QNAME), sourceProvider),
+            NetconfSessionPreferences.fromStrings(
+                Set.of(TEST_NAMESPACE + "?module=" + TEST_MODULE + "&amp;revision=" + TEST_REVISION)));
+
+        final var result = Futures.getDone(setup.startResolution());
+        assertEquals(Set.of(new AvailableCapabilityBuilder()
+            .setCapability("(test:namespace?revision=2013-07-22)test-module")
+            .setCapabilityOrigin(CapabilityOrigin.DeviceAdvertised)
+            .build()), result.capabilities().resolvedCapabilities());
+    }
+}