From: Robert Varga Date: Wed, 21 Feb 2024 10:27:47 +0000 (+0100) Subject: Eliminate SchemaResourcesDTO X-Git-Tag: v7.0.0~9 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=14656d8a13dc04af3b7b0ba7d042b6e012a252f5;p=netconf.git Eliminate SchemaResourcesDTO 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 --- diff --git a/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/NetconfTopologyManager.java b/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/NetconfTopologyManager.java index bad929cca0..66cb2b7f73 100644 --- a/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/NetconfTopologyManager.java +++ b/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/NetconfTopologyManager.java @@ -326,7 +326,8 @@ public class NetconfTopologyManager implements DataTreeChangeListener, Aut .setSchemaAssembler(schemaAssembler) .setTopologyId(topologyId) .setNetconfClientFactory(clientFactory) - .setSchemaResourceDTO(resourceManager.getSchemaResources(netconfNode.getSchemaCacheDirectory(), deviceId)) + .setDeviceSchemaProvider(resourceManager.getSchemaResources(netconfNode.getSchemaCacheDirectory(), + deviceId)) .setIdleTimeout(writeTxIdleTimeout) .build(); } diff --git a/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/actors/NetconfNodeActor.java b/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/actors/NetconfNodeActor.java index 21bf46a8a4..12b8350372 100644 --- a/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/actors/NetconfNodeActor.java +++ b/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/actors/NetconfNodeActor.java @@ -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 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, diff --git a/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/utils/NetconfTopologySetup.java b/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/utils/NetconfTopologySetup.java index 08286fe4c9..7ec8e10206 100644 --- a/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/utils/NetconfTopologySetup.java +++ b/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/utils/NetconfTopologySetup.java @@ -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) { diff --git a/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/MountPointEndToEndTest.java b/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/MountPointEndToEndTest.java index cbc5839beb..b3989112f5 100644 --- a/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/MountPointEndToEndTest.java +++ b/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/MountPointEndToEndTest.java @@ -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)), diff --git a/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeActorTest.java b/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeActorTest.java index a2f3f12cb8..19fd22da6f 100644 --- a/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeActorTest.java +++ b/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeActorTest.java @@ -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)); diff --git a/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeManagerTest.java b/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeManagerTest.java index 168bd61350..69d02be478 100644 --- a/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeManagerTest.java +++ b/apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeManagerTest.java @@ -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); diff --git a/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandler.java b/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandler.java index 6ec5205a4e..de77e29a2f 100644 --- a/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandler.java +++ b/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandler.java @@ -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 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(); 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()))); diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java index afbfb83254..70c661fae3 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java @@ -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 forDevice(final NetconfDeviceRpc deviceRpc, + public static ListenableFuture forDevice(final NetconfRpcService deviceRpc, final RemoteDeviceId deviceId) { final var future = SettableFuture.create(); Futures.addCallback(deviceRpc.invokeNetconf(Get.QNAME, GET_MODULES_STATE_MODULE_LIST_RPC), diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProvider.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProvider.java index 4c50943c45..6ca3b000bf 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProvider.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProvider.java @@ -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 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 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()) diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDevice.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDevice.java index 8260b755f1..b743a1e91a 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDevice.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDevice.java @@ -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 { 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 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> 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 { @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 { 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 { 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 { 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 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 { 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 { - private final SettableFuture resultFuture = SettableFuture.create(); - - private final Set nonModuleBasedCapabilities = new HashSet<>(); - private final Map unresolvedCapabilites = new HashMap<>(); - private final Set resolvedCapabilities = new HashSet<>(); - - private final DeviceSources deviceSources; - private final NetconfSessionPreferences remoteSessionCapabilities; - - private Collection 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 missingSources = filterMissingSources(requiredSources); - - addUnresolvedCapabilities(getQNameFromSourceIdentifiers(missingSources), - UnavailableCapability.FailureReason.MissingSource); - requiredSources.removeAll(missingSources); - } - - ListenableFuture startResolution() { - trySetupSchema(); - return resultFuture; - } - - @Override - public void onSuccess(final EffectiveModelContext result) { - LOG.debug("{}: Schema context built successfully from {}", id, requiredSources); - - final Collection 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 filterMissingSources(final Collection 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 capabilities, final FailureReason reason) { - for (QName s : capabilities) { - unresolvedCapabilites.put(s, reason); - } - } - - private List 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 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 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 getQNameFromSourceIdentifiers(final Collection identifiers) { - final Collection 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; - } - } } diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceBuilder.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceBuilder.java index e97b231317..40701654a8 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceBuilder.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceBuilder.java @@ -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); } } diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceSchema.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceSchema.java index 4805400158..03db013bc4 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceSchema.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceSchema.java @@ -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, diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemas.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemas.java index c8e2ae8349..41b2beefb1 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemas.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemas.java @@ -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 forDevice(final DOMRpcService deviceRpc, + static ListenableFuture 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.create(); - Futures.addCallback(deviceRpc.invokeRpc(Get.QNAME, GET_SCHEMAS_RPC), + Futures.addCallback(deviceRpc.invokeNetconf(Get.QNAME, GET_SCHEMAS_RPC), new FutureCallback() { @Override public void onSuccess(final DOMRpcResult result) { diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java index cd6d77283b..ca8d8d8611 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java @@ -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 resolve(final NetconfDeviceRpc deviceRpc, + public ListenableFuture 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)) { diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/BaseNetconfSchemaProvider.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/BaseNetconfSchemaProvider.java index ff7883c16f..5b17882df8 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/BaseNetconfSchemaProvider.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/BaseNetconfSchemaProvider.java @@ -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 index 0000000000..16fe8a1e8b --- /dev/null +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/DeviceNetconfSchema.java @@ -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 index 0000000000..b0b7236eee --- /dev/null +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/DeviceNetconfSchemaProvider.java @@ -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 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(); +} diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java index 4142a666e9..e72dbb5a88 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/NetconfDeviceSchemasResolver.java @@ -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 resolve(NetconfDeviceRpc deviceRpc, + ListenableFuture resolve(NetconfRpcService deviceRpc, NetconfSessionPreferences remoteSessionCapabilities, RemoteDeviceId id, EffectiveModelContext schemaContext); } diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/SchemaResourceManager.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/SchemaResourceManager.java index c41bea7800..82ec3d2aca 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/SchemaResourceManager.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/SchemaResourceManager.java @@ -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 index 0000000000..a65aaba4b9 --- /dev/null +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultDeviceNetconfSchemaProvider.java @@ -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 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; + } +} diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSchemaResourceManager.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSchemaResourceManager.java index 0d59f0edfb..5942a5ceac 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSchemaResourceManager.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DefaultSchemaResourceManager.java @@ -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 resources = new HashMap<>(); - private final @NonNull SchemaResourcesDTO defaultResources; + private final Map 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); } } diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/DeviceSources.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DeviceSources.java 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 d7d2d54a80..39de902c51 100644 --- a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/DeviceSources.java +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/DeviceSources.java @@ -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 index 0000000000..d93460fa4b --- /dev/null +++ b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetup.java @@ -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 { + private static final Logger LOG = LoggerFactory.getLogger(SchemaSetup.class); + + private final SettableFuture resultFuture = SettableFuture.create(); + private final Set nonModuleBasedCapabilities = new HashSet<>(); + private final Map unresolvedCapabilites = new HashMap<>(); + private final Set 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 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 startResolution() { + trySetupSchema(); + return resultFuture; + } + + @Override + public void onSuccess(final EffectiveModelContext result) { + LOG.debug("{}: Schema context built successfully from {}", deviceId, requiredSources); + + final Collection 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 filterMissingSources(final Collection 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 capabilities, final FailureReason reason) { + for (QName s : capabilities) { + unresolvedCapabilites.put(s, reason); + } + } + + private List 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 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 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 getQNameFromSourceIdentifiers(final Collection identifiers) { + final Collection 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; + } +} diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractBaseSchemasTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractBaseSchemasTest.java index c67c344d75..85484243b4 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractBaseSchemasTest.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractBaseSchemasTest.java @@ -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()); } diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractTestModelTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractTestModelTest.java index c61f76b7dd..87f843e207 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractTestModelTest.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/AbstractTestModelTest.java @@ -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"); } diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProviderTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProviderTest.java index 966e79fe00..823266c913 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProviderTest.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/MonitoringSchemaSourceProviderTest.java @@ -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 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"))); } diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java index b2f8a97678..8044aee8b0 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfDeviceTest.java @@ -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 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.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 + "&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 + "&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 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 + "&revision=" + TEST_REVISION)); + final var facade = getFacade(); + final var schemaFuture = SettableFuture.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 + "&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 schemaFuture = SettableFuture.create(); + final var schemaContextProviderFactory = mock(EffectiveModelContextFactory.class); + final var 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() + 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 + "&revision=" + TEST_REVISION)); + final var sessionCaps = getSessionCaps(true, + TEST_NAMESPACE + "?module=" + TEST_MODULE + "&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 + "&revision=" + TEST_REVISION)); - final Map moduleBasedCaps = new HashMap<>(); + final var sessionCaps = getSessionCaps(true, + TEST_NAMESPACE + "?module=" + TEST_MODULE + "&revision=" + TEST_REVISION); + final var moduleBasedCaps = new HashMap(); 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 + "&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 additionalCapabilities) { + final String... additionalCapabilities) { final var capabilities = new ArrayList(); 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); } diff --git a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasTest.java b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasTest.java index 5dfebde791..4fb75588b0 100644 --- a/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasTest.java +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasTest.java @@ -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 index 0000000000..d2e1be0558 --- /dev/null +++ b/plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/impl/SchemaSetupTest.java @@ -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 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 + "&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()); + } +}