Disconnect NetconfDeviceRpc from DOMRpcService 61/110061/6
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 28 Jan 2024 11:32:11 +0000 (12:32 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Sun, 28 Jan 2024 18:00:03 +0000 (19:00 +0100)
DOMService is about to get very picky about class hierarchy, let's make
sure we do not attempt to combine multiple DOMServices.

This adds a bit of indirection, but opens up the possibility to properly
separate invocation paths and their expectations.

Change-Id: Iecdb60e9664a97c400eab5100ef05a84e3f555e1
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
19 files changed:
apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/ProxyDOMRpcService.java
apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/actors/NetconfNodeActor.java
apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/MountPointEndToEndTest.java
apps/netconf-topology-singleton/src/test/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeActorTest.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/DeviceSourcesResolver.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/LibraryModulesSchemas.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfDevice.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/NetconfStateSchemasResolverImpl.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/api/RemoteDeviceServices.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/spi/KeepaliveSalFacade.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/spi/NetconfDeviceDOMRpcService.java [new file with mode: 0644]
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/spi/NetconfDeviceMount.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/spi/NetconfDeviceRpc.java
plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/spi/SchemalessNetconfDeviceRpc.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/spi/KeepaliveSalFacadeResponseWaitingTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/spi/KeepaliveSalFacadeTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/spi/MountInstanceTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/spi/NetconfDeviceRpcTest.java
plugins/netconf-client-mdsal/src/test/java/org/opendaylight/netconf/client/mdsal/spi/SchemalessNetconfDeviceRpcTest.java

index b779b9a38fb2954521b754310b47a868e7368885..d527704e2a742ccf1803bf0f40177ab9b49837ea 100644 (file)
@@ -18,9 +18,9 @@ import com.google.common.util.concurrent.SettableFuture;
 import java.util.Collection;
 import org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener;
 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.RemoteDeviceId;
-import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Rpcs;
 import org.opendaylight.netconf.topology.singleton.impl.utils.ClusteringRpcException;
 import org.opendaylight.netconf.topology.singleton.messages.NormalizedNodeMessage;
 import org.opendaylight.netconf.topology.singleton.messages.SchemaPathMessage;
@@ -36,7 +36,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import scala.concurrent.Future;
 
-public class ProxyDOMRpcService implements Rpcs.Normalized {
+public class ProxyDOMRpcService implements DOMRpcService {
     private static final Logger LOG = LoggerFactory.getLogger(ProxyDOMRpcService.class);
 
     private final ActorRef masterActorRef;
index 679c63bfbc9236dda13a66926ee12178af42b90b..1cf783775faf846575504809d4a7e9efac21f9ce 100644 (file)
@@ -129,7 +129,7 @@ public class NetconfNodeActor extends AbstractUntypedActor {
             readTxActor = context().actorOf(ReadTransactionActor.props(tx));
 
             final var deviceServices = masterActorData.getDeviceServices();
-            deviceRpc = deviceServices.rpcs() instanceof Rpcs.Normalized normalized ? normalized : null;
+            deviceRpc = deviceServices.rpcs() instanceof Rpcs.Normalized normalized ? normalized.domRpcService() : null;
             deviceAction = deviceServices.actions() instanceof Actions.Normalized normalized ? normalized : null;
 
             sender().tell(new MasterActorDataInitialized(), self());
@@ -213,10 +213,8 @@ public class NetconfNodeActor extends AbstractUntypedActor {
     }
 
     private void sendYangTextSchemaSourceProxy(final SourceIdentifier sourceIdentifier, final ActorRef sender) {
-        final ListenableFuture<YangTextSchemaSource> schemaSourceFuture =
-                schemaRepository.getSchemaSource(sourceIdentifier, YangTextSchemaSource.class);
-
-        Futures.addCallback(schemaSourceFuture, new FutureCallback<YangTextSchemaSource>() {
+        final var schemaSourceFuture = schemaRepository.getSchemaSource(sourceIdentifier, YangTextSchemaSource.class);
+        Futures.addCallback(schemaSourceFuture, new FutureCallback<>() {
             @Override
             public void onSuccess(final YangTextSchemaSource yangTextSchemaSource) {
                 try {
@@ -332,7 +330,7 @@ public class NetconfNodeActor extends AbstractUntypedActor {
             final SlaveSalFacade localSlaveSalManager, final ActorRef masterReference, final int tries) {
         final ListenableFuture<EffectiveModelContext> schemaContextFuture =
                 schemaContextFactory.createEffectiveModelContext(sourceIdentifiers);
-        Futures.addCallback(schemaContextFuture, new FutureCallback<EffectiveModelContext>() {
+        Futures.addCallback(schemaContextFuture, new FutureCallback<>() {
             @Override
             public void onSuccess(final EffectiveModelContext result) {
                 executeInSelf(() -> {
@@ -342,8 +340,10 @@ public class NetconfNodeActor extends AbstractUntypedActor {
                         LOG.info("{}: Schema context resolved: {} - registering slave mount point",
                                 id, result.getModules());
                         final var actorSystem = setup.getActorSystem();
+                        final var rpcProxy = new ProxyDOMRpcService(actorSystem, masterReference, id,
+                            actorResponseWaitTime);
                         slaveSalManager.registerSlaveMountPoint(result, masterReference, new RemoteDeviceServices(
-                            new ProxyDOMRpcService(actorSystem, masterReference, id, actorResponseWaitTime),
+                            (Rpcs.Normalized) () -> rpcProxy,
                             new ProxyDOMActionService(actorSystem, masterReference, id, actorResponseWaitTime)));
                     }
                 });
index 8c280a68e2ddbbe24eb0d209311b221cddeb4a89..8cc00f1e92dc4a0d57b362f870d3039f0630ea9e 100644 (file)
@@ -73,7 +73,6 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
 import org.opendaylight.mdsal.dom.api.DOMMountPointListener;
 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
-import org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener;
 import org.opendaylight.mdsal.dom.api.DOMRpcIdentifier;
 import org.opendaylight.mdsal.dom.api.DOMRpcImplementation;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
@@ -130,7 +129,6 @@ import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
-import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.concepts.Registration;
 import org.opendaylight.yangtools.util.concurrent.FluentFutures;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -258,18 +256,7 @@ public class MountPointEndToEndTest extends AbstractBaseSchemasTest {
                 DOMRpcIdentifier.create(putTopRpcSchemaPath), DOMRpcIdentifier.create(getTopRpcSchemaPath));
 
         final var rpcService = router.getRpcService();
-        deviceRpcService = new Rpcs.Normalized() {
-            @Override
-            public ListenableFuture<? extends DOMRpcResult> invokeRpc(final QName type, final ContainerNode input) {
-                return rpcService.invokeRpc(type, input);
-            }
-
-            @Override
-            public <T extends DOMRpcAvailabilityListener> ListenerRegistration<T> registerRpcListener(
-                    final T listener) {
-                return rpcService.registerRpcListener(listener);
-            }
-        };
+        deviceRpcService = () -> rpcService;
 
         builderFactory = new NetconfClientConfigurationBuilderFactoryImpl(mockEncryptionService, credentialProvider,
             sslHandlerFactoryProvider);
index 93b339576e995bd88304269bde6ff17edc5a20d6..53104ca035d043ad4b835cab34d77acd2ce48a9b 100644 (file)
@@ -142,7 +142,9 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
     private final SharedSchemaRepository masterSchemaRepository = new SharedSchemaRepository("master");
 
     @Mock
-    private Rpcs.Normalized mockDOMRpcService;
+    private Rpcs.Normalized mockRpc;
+    @Mock
+    private DOMRpcService mockDOMRpcService;
     @Mock
     private Actions.Normalized mockDOMActionService;
     @Mock
@@ -198,8 +200,10 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
         doReturn(mockSchemaSourceReg1).when(mockRegistry).registerSchemaSource(any(), withSourceId(SOURCE_IDENTIFIER1));
         doReturn(mockSchemaSourceReg2).when(mockRegistry).registerSchemaSource(any(), withSourceId(SOURCE_IDENTIFIER2));
 
-        doReturn(mockSchemaContextFactory).when(mockSchemaRepository)
-                .createEffectiveModelContextFactory();
+        doReturn(mockSchemaContextFactory).when(mockSchemaRepository).createEffectiveModelContextFactory();
+
+        doReturn(mockDOMRpcService).when(mockRpc).domRpcService();
+
     }
 
     @After
@@ -598,7 +602,7 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
         initializeMaster(List.of());
         registerSlaveMountPoint();
 
-        ArgumentCaptor<DOMDataBroker> domDataBrokerCaptor = ArgumentCaptor.forClass(DOMDataBroker.class);
+        final var domDataBrokerCaptor = ArgumentCaptor.forClass(DOMDataBroker.class);
         verify(mockMountPointBuilder).addService(eq(DOMDataBroker.class), domDataBrokerCaptor.capture());
 
         final DOMDataBroker slaveDOMDataBroker = domDataBrokerCaptor.getValue();
@@ -688,7 +692,7 @@ public class NetconfNodeActorTest extends AbstractBaseSchemasTest {
 
     private void initializeMaster(final List<SourceIdentifier> sourceIdentifiers) {
         masterRef.tell(new CreateInitialMasterActorData(mockDOMDataBroker, netconfService, sourceIdentifiers,
-                new RemoteDeviceServices(mockDOMRpcService, mockDOMActionService)), testKit.getRef());
+                new RemoteDeviceServices(mockRpc, mockDOMActionService)), testKit.getRef());
         testKit.expectMsgClass(MasterActorDataInitialized.class);
     }
 
index f2691a27a29711fc2b547dc315aa9eba9a8f23e0..57b5050978e51d70b0aa4054fd8dcaa9ab89696f 100644 (file)
@@ -74,7 +74,7 @@ final class DeviceSourcesResolver implements Callable<DeviceSources> {
 
         final var sourceProvider = availableSchemas instanceof LibraryModulesSchemas libraryModule
             ? new LibrarySchemaSourceProvider(id, libraryModule.getAvailableModels())
-                : new MonitoringSchemaSourceProvider(id, deviceRpc);
+                : new MonitoringSchemaSourceProvider(id, deviceRpc.domRpcService());
         return new DeviceSources(requiredSources, providedSources, sourceProvider);
     }
 }
\ No newline at end of file
index 0a4288c12e664dd459891e81ac595399665bdb0a..abe1626465cddff2b32a89543cc221b216bef6fb 100644 (file)
@@ -163,7 +163,8 @@ public final class LibraryModulesSchemas implements NetconfDeviceSchemas {
     public static LibraryModulesSchemas create(final NetconfDeviceRpc deviceRpc, final RemoteDeviceId deviceId) {
         final DOMRpcResult moduleListNodeResult;
         try {
-            moduleListNodeResult = deviceRpc.invokeRpc(Get.QNAME, GET_MODULES_STATE_MODULE_LIST_RPC).get();
+            moduleListNodeResult = deviceRpc.domRpcService().invokeRpc(Get.QNAME, GET_MODULES_STATE_MODULE_LIST_RPC)
+                .get();
         } catch (final InterruptedException e) {
             Thread.currentThread().interrupt();
             throw new IllegalStateException(deviceId + ": Interrupted while waiting for response to "
index d87e37d63a508d64ca5c05d1aa1a0c400d07b1df..d349afd2f346dd9a9887ff0b38ac8f83d835a74f 100644 (file)
@@ -187,10 +187,11 @@ public class NetconfDevice implements RemoteDevice<NetconfDeviceCommunicator> {
         // 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.invokeRpc(CreateSubscription.QNAME, Builders.containerBuilder()
-            .withNodeIdentifier(NodeIdentifier.create(CreateSubscriptionInput.QNAME))
-            // Note: default 'stream' is 'NETCONF', we do not need to create an explicit leaf
-            .build());
+        final var rpcResultListenableFuture = deviceRpc.domRpcService()
+            .invokeRpc(CreateSubscription.QNAME, Builders.containerBuilder()
+                .withNodeIdentifier(NodeIdentifier.create(CreateSubscriptionInput.QNAME))
+                // Note: default 'stream' is 'NETCONF', we do not need to create an explicit leaf
+                .build());
 
         Futures.addCallback(rpcResultListenableFuture, new FutureCallback<DOMRpcResult>() {
             @Override
@@ -287,7 +288,7 @@ public class NetconfDevice implements RemoteDevice<NetconfDeviceCommunicator> {
         final NetconfDeviceRpc deviceRpc = new NetconfDeviceRpc(schemaContext, listener,
             new NetconfMessageTransformer(emptyContext, false, baseSchema));
 
-        return Futures.transform(deviceRpc.invokeRpc(Get.QNAME, Builders.containerBuilder()
+        return Futures.transform(deviceRpc.domRpcService().invokeRpc(Get.QNAME, Builders.containerBuilder()
             .withNodeIdentifier(NETCONF_GET_NODEID)
             .withChild(NetconfMessageTransformUtil.toFilterStructure(RFC8528_SCHEMA_MOUNTS, schemaContext))
             .build()), rpcResult -> processSchemaMounts(rpcResult, emptyContext), MoreExecutors.directExecutor());
index bfe53ad72cc5d02311949ed74af21f3efac1cc16..481a78408c62cade2244344bcfc92ba13d64f826 100644 (file)
@@ -32,7 +32,7 @@ public final class NetconfStateSchemasResolverImpl implements NetconfDeviceSchem
             final RemoteDeviceId id, final EffectiveModelContext schemaContext) {
         // FIXME: I think we should prefer YANG library here
         if (remoteSessionCapabilities.isMonitoringSupported()) {
-            return NetconfStateSchemas.create(deviceRpc, remoteSessionCapabilities, id, schemaContext);
+            return NetconfStateSchemas.create(deviceRpc.domRpcService(), remoteSessionCapabilities, id, schemaContext);
         }
         if (remoteSessionCapabilities.containsModuleCapability(RFC8525_YANG_LIBRARY_CAPABILITY)
                 || remoteSessionCapabilities.containsModuleCapability(RFC7895_YANG_LIBRARY_CAPABILITY)) {
index ec1e5e966704a110eaff64943e543c100fa87c55..64d40aec8f2e580bdc6231ac0a5d78742f77431d 100644 (file)
@@ -15,8 +15,6 @@ import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.dom.api.DOMActionService;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
-import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Actions;
-import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Rpcs;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 
@@ -36,19 +34,22 @@ public record RemoteDeviceServices(@NonNull Rpcs rpcs, @Nullable Actions actions
         /**
          * NETCONF device RPCs operating just as any other {@link DOMRpcService}.
          */
-        non-sealed interface Normalized extends Rpcs, DOMRpcService {
+        non-sealed interface Normalized extends Rpcs {
             @Override
             default ListenableFuture<? extends DOMRpcResult> invokeNetconf(final QName type,
                     final ContainerNode input) {
-                return invokeRpc(type, input);
+                return domRpcService().invokeRpc(type, input);
             }
+
+            @NonNull DOMRpcService domRpcService();
         }
 
         /**
          * NETCONF device RPCs operating in terms of {@link SchemalessRpcService}.
          */
-        non-sealed interface Schemaless extends Rpcs, SchemalessRpcService {
-            // Just an interface combination
+        non-sealed interface Schemaless extends Rpcs {
+
+            @NonNull SchemalessRpcService schemalessRpcService();
         }
     }
 
index d9551a7b026bed17628ada2fe858f838bc54f942..8517cbf70a5799c6fd46abe611942baeefc40a00 100644 (file)
@@ -28,6 +28,7 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.dom.api.DOMNotification;
 import org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.netconf.client.mdsal.NetconfDeviceCommunicator;
 import org.opendaylight.netconf.client.mdsal.NetconfDeviceSchema;
 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
@@ -35,6 +36,7 @@ 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.api.RemoteDeviceServices.Rpcs;
+import org.opendaylight.netconf.client.mdsal.api.SchemalessRpcService;
 import org.opendaylight.netconf.client.mdsal.impl.NetconfMessageTransformUtil;
 import org.opendaylight.netconf.common.NetconfTimer;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.GetConfig;
@@ -356,10 +358,32 @@ public final class KeepaliveSalFacade implements RemoteDeviceHandler {
      * invocation. Version for {@link Rpcs.Normalized}.
      */
     private final class NormalizedKeepaliveRpcs implements Rpcs.Normalized {
+        private final @NonNull KeepaliveDOMRpcService domRpcService;
         private final Rpcs.Normalized delegate;
 
         NormalizedKeepaliveRpcs(final Rpcs.Normalized delegate) {
             this.delegate = requireNonNull(delegate);
+            domRpcService = new KeepaliveDOMRpcService(delegate.domRpcService());
+        }
+
+        @Override
+        public ListenableFuture<? extends DOMRpcResult> invokeNetconf(final QName type, final ContainerNode input) {
+            // FIXME: what happens if we disable keepalive and then invokeRpc() throws?
+            disableKeepalive();
+            return scheduleTimeout(delegate.invokeNetconf(type, input));
+        }
+
+        @Override
+        public DOMRpcService domRpcService() {
+            return domRpcService;
+        }
+    }
+
+    private final class KeepaliveDOMRpcService implements DOMRpcService {
+        private final @NonNull DOMRpcService delegate;
+
+        KeepaliveDOMRpcService(final DOMRpcService delegate) {
+            this.delegate = requireNonNull(delegate);
         }
 
         @Override
@@ -371,10 +395,11 @@ public final class KeepaliveSalFacade implements RemoteDeviceHandler {
 
         @Override
         public <T extends DOMRpcAvailabilityListener> ListenerRegistration<T> registerRpcListener(
-            final T rpcListener) {
+                final T rpcListener) {
             // There is no real communication with the device (yet), hence no recordActivity() or anything
             return delegate.registerRpcListener(rpcListener);
         }
+
     }
 
     /**
@@ -382,10 +407,12 @@ public final class KeepaliveSalFacade implements RemoteDeviceHandler {
      * invocation. Version for {@link Rpcs.Schemaless}.
      */
     private final class SchemalessKeepaliveRpcs implements Rpcs.Schemaless {
+        private final @NonNull KeepaliveSchemalessRpcService schemalessRpcService;
         private final Rpcs.Schemaless delegate;
 
         SchemalessKeepaliveRpcs(final Rpcs.Schemaless delegate) {
             this.delegate = requireNonNull(delegate);
+            schemalessRpcService = new KeepaliveSchemalessRpcService(delegate.schemalessRpcService());
         }
 
         @Override
@@ -396,10 +423,23 @@ public final class KeepaliveSalFacade implements RemoteDeviceHandler {
         }
 
         @Override
-        public ListenableFuture<? extends DOMSource> invokeRpc(final QName type, final DOMSource input) {
+        public SchemalessRpcService schemalessRpcService() {
+            return schemalessRpcService;
+        }
+    }
+
+    private final class KeepaliveSchemalessRpcService implements SchemalessRpcService {
+        private final SchemalessRpcService delegate;
+
+        KeepaliveSchemalessRpcService(final SchemalessRpcService delegate) {
+            this.delegate = requireNonNull(delegate);
+        }
+
+        @Override
+        public ListenableFuture<? extends DOMSource> invokeRpc(final QName type, final DOMSource payload) {
             // FIXME: what happens if we disable keepalive and then invokeRpc() throws?
             disableKeepalive();
-            return scheduleTimeout(delegate.invokeRpc(type, input));
+            return scheduleTimeout(delegate.invokeRpc(type, payload));
         }
     }
 }
diff --git a/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/spi/NetconfDeviceDOMRpcService.java b/plugins/netconf-client-mdsal/src/main/java/org/opendaylight/netconf/client/mdsal/spi/NetconfDeviceDOMRpcService.java
new file mode 100644 (file)
index 0000000..3ef6925
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.spi;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.Collections2;
+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 org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener;
+import org.opendaylight.mdsal.dom.api.DOMRpcIdentifier;
+import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
+import org.opendaylight.mdsal.dom.api.DOMRpcResult;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
+import org.opendaylight.mdsal.dom.api.DefaultDOMRpcException;
+import org.opendaylight.netconf.api.messages.NetconfMessage;
+import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceCommunicator;
+import org.opendaylight.netconf.client.mdsal.api.RpcTransformer;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.concepts.NoOpListenerRegistration;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+
+record NetconfDeviceDOMRpcService(
+        EffectiveModelContext modelContext,
+        RemoteDeviceCommunicator communicator,
+        RpcTransformer<ContainerNode, DOMRpcResult> transformer) implements DOMRpcService {
+    NetconfDeviceDOMRpcService {
+        requireNonNull(modelContext);
+    }
+
+    @Override
+    @SuppressWarnings("checkstyle:IllegalCatch")
+    public ListenableFuture<DOMRpcResult> invokeRpc(final QName type, final ContainerNode input) {
+        final var delegateFuture = communicator.sendRequest(transformer.toRpcRequest(type, input), type);
+
+        final var ret = SettableFuture.<DOMRpcResult>create();
+        Futures.addCallback(delegateFuture, new FutureCallback<>() {
+            @Override
+            public void onSuccess(final RpcResult<NetconfMessage> result) {
+                final DOMRpcResult rpcResult;
+                try {
+                    rpcResult = transformer.toRpcResult(result, type);
+                } catch (Exception cause) {
+                    ret.setException(new DefaultDOMRpcException(
+                        "Unable to parse rpc reply. type: " + type + " input: " + input, cause));
+                    return;
+                }
+
+                ret.set(rpcResult);
+            }
+
+            @Override
+            public void onFailure(final Throwable cause) {
+                ret.setException(new DOMRpcImplementationNotAvailableException(cause, "Unable to invoke rpc %s",
+                    type));
+            }
+
+        }, MoreExecutors.directExecutor());
+        return ret;
+    }
+
+    @Override
+    public <T extends DOMRpcAvailabilityListener> ListenerRegistration<T> registerRpcListener(final T listener) {
+        listener.onRpcAvailable(Collections2.transform(modelContext.getOperations(),
+            input -> DOMRpcIdentifier.create(input.getQName())));
+        return NoOpListenerRegistration.of(listener);
+    }
+}
\ No newline at end of file
index eddce47171d676c0a30b344e5f7338a52dbde567..0be46207c1a3300c53f66c264c20209e24013513 100644 (file)
@@ -69,9 +69,9 @@ public class NetconfDeviceMount implements AutoCloseable {
         final var rpcs = services.rpcs();
         mountBuilder.addService(NetconfRpcService.class, rpcs);
         if (rpcs instanceof Rpcs.Normalized normalized) {
-            mountBuilder.addService(DOMRpcService.class, normalized);
+            mountBuilder.addService(DOMRpcService.class, normalized.domRpcService());
         } else if (rpcs instanceof Rpcs.Schemaless schemaless) {
-            mountBuilder.addService(SchemalessRpcService.class, schemaless);
+            mountBuilder.addService(SchemalessRpcService.class, schemaless.schemalessRpcService());
         }
         if (services.actions() instanceof Actions.Normalized normalized) {
             mountBuilder.addService(DOMActionService.class, normalized);
index 565ddabb50593df9493cc41146287c5827f67bff..63e965a05a23efc7f2550f39cfe3a925ada81c9e 100644 (file)
@@ -7,27 +7,12 @@
  */
 package org.opendaylight.netconf.client.mdsal.spi;
 
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.collect.Collections2;
-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 org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener;
-import org.opendaylight.mdsal.dom.api.DOMRpcIdentifier;
-import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
-import org.opendaylight.mdsal.dom.api.DefaultDOMRpcException;
-import org.opendaylight.netconf.api.messages.NetconfMessage;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceCommunicator;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Rpcs;
 import org.opendaylight.netconf.client.mdsal.api.RpcTransformer;
-import org.opendaylight.yangtools.concepts.ListenerRegistration;
-import org.opendaylight.yangtools.concepts.NoOpListenerRegistration;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 
@@ -36,53 +21,15 @@ import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
  * {@link ContainerNode}.
  */
 public final class NetconfDeviceRpc implements Rpcs.Normalized {
-    private final RemoteDeviceCommunicator communicator;
-    private final RpcTransformer<ContainerNode, DOMRpcResult> transformer;
-    private final EffectiveModelContext modelContext;
+    private final @NonNull NetconfDeviceDOMRpcService domRpcService;
 
     public NetconfDeviceRpc(final EffectiveModelContext modelContext, final RemoteDeviceCommunicator communicator,
             final RpcTransformer<ContainerNode, DOMRpcResult> transformer) {
-        this.modelContext = requireNonNull(modelContext);
-        this.communicator = communicator;
-        this.transformer = transformer;
+        domRpcService = new NetconfDeviceDOMRpcService(modelContext, communicator, transformer);
     }
 
     @Override
-    @SuppressWarnings("checkstyle:IllegalCatch")
-    public ListenableFuture<DOMRpcResult> invokeRpc(final QName type, final ContainerNode input) {
-        final var delegateFuture = communicator.sendRequest(transformer.toRpcRequest(type, input), type);
-
-        final var ret = SettableFuture.<DOMRpcResult>create();
-        Futures.addCallback(delegateFuture, new FutureCallback<>() {
-            @Override
-            public void onSuccess(final RpcResult<NetconfMessage> result) {
-                final DOMRpcResult rpcResult;
-                try {
-                    rpcResult = transformer.toRpcResult(result, type);
-                } catch (Exception cause) {
-                    ret.setException(new DefaultDOMRpcException(
-                        "Unable to parse rpc reply. type: " + type + " input: " + input, cause));
-                    return;
-                }
-
-                ret.set(rpcResult);
-            }
-
-            @Override
-            public void onFailure(final Throwable cause) {
-                ret.setException(new DOMRpcImplementationNotAvailableException(cause, "Unable to invoke rpc %s", type));
-            }
-
-        }, MoreExecutors.directExecutor());
-        return ret;
-    }
-
-    @Override
-    public <T extends DOMRpcAvailabilityListener> ListenerRegistration<T> registerRpcListener(final T listener) {
-        listener.onRpcAvailable(Collections2.transform(modelContext.getOperations(),
-            input -> DOMRpcIdentifier.create(input.getQName())));
-
-        // NOOP, no rpcs appear and disappear in this implementation
-        return NoOpListenerRegistration.of(listener);
+    public DOMRpcService domRpcService() {
+        return domRpcService;
     }
 }
index 4008f7f1af6c878061e96b53046fad8beb2c25f3..139a5752dcaf2cb426ce79c741733d0f7691673e 100644 (file)
@@ -12,7 +12,6 @@ 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 javax.xml.transform.dom.DOMSource;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
@@ -21,6 +20,7 @@ import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceCommunicator;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices.Rpcs;
 import org.opendaylight.netconf.client.mdsal.api.RpcTransformer;
+import org.opendaylight.netconf.client.mdsal.api.SchemalessRpcService;
 import org.opendaylight.netconf.client.mdsal.impl.BaseRpcSchemalessTransformer;
 import org.opendaylight.netconf.client.mdsal.impl.SchemalessMessageTransformer;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -29,9 +29,10 @@ import org.opendaylight.yangtools.yang.common.YangConstants;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 
 /**
- * Invokes RPC by sending netconf message via listener. Also transforms result from NetconfMessage to CompositeNode.
+ * Invokes RPC by sending NETCONF message via listener. Also transforms result from NetconfMessage to CompositeNode.
  */
 public final class SchemalessNetconfDeviceRpc implements Rpcs.Schemaless {
+    private final @NonNull SchemalessRpcService schemalessRpcService;
     private final RemoteDeviceCommunicator listener;
     private final BaseRpcSchemalessTransformer baseRpcTransformer;
     private final SchemalessMessageTransformer schemalessTransformer;
@@ -44,6 +45,7 @@ public final class SchemalessNetconfDeviceRpc implements Rpcs.Schemaless {
         this.listener = listener;
         this.baseRpcTransformer = baseRpcTransformer;
         schemalessTransformer = messageTransformer;
+        schemalessRpcService = (type, input) -> handleRpc(type, input, schemalessTransformer);
     }
 
     @Override
@@ -55,8 +57,8 @@ public final class SchemalessNetconfDeviceRpc implements Rpcs.Schemaless {
     }
 
     @Override
-    public ListenableFuture<? extends DOMSource> invokeRpc(final QName type, final DOMSource input) {
-        return handleRpc(type, input, schemalessTransformer);
+    public SchemalessRpcService schemalessRpcService() {
+        return schemalessRpcService;
     }
 
     private @NonNull <I, R> ListenableFuture<R> handleRpc(final @NonNull QName type,
index b6fe7827700481fa4c4f56c6d2a1537ac55b8926..a73e74b0dfca98ec8a377d060b8593af892f9d84 100644 (file)
@@ -23,6 +23,7 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.opendaylight.mdsal.dom.api.DOMNotification;
 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.NetconfDeviceCommunicator;
 import org.opendaylight.netconf.client.mdsal.NetconfDeviceSchema;
@@ -50,6 +51,8 @@ class KeepaliveSalFacadeResponseWaitingTest {
     @Mock
     private Rpcs.Normalized deviceRpc;
     @Mock
+    private DOMRpcService deviceDomRpc;
+    @Mock
     private NetconfDeviceCommunicator listener;
     private DefaultNetconfTimer timer;
 
@@ -78,7 +81,8 @@ class KeepaliveSalFacadeResponseWaitingTest {
         //This settable future object will be never set to any value. The test wants to simulate waiting for the result
         //of the future object.
         final var settableFuture = SettableFuture.<DOMRpcResult>create();
-        doReturn(settableFuture).when(deviceRpc).invokeRpc(null, null);
+        doReturn(settableFuture).when(deviceDomRpc).invokeRpc(null, null);
+        doReturn(deviceDomRpc).when(deviceRpc).domRpcService();
 
         //This settable future will be used to check the invokation of keepalive RPC. Should be never invoked.
         final var keepaliveSettableFuture = SettableFuture.<DOMRpcResult>create();
@@ -94,10 +98,10 @@ class KeepaliveSalFacadeResponseWaitingTest {
         underlyingSalFacade.invokeNullRpc();
 
         //Invoking of general RPC.
-        verify(deviceRpc, after(2000).times(1)).invokeRpc(null, null);
+        verify(deviceDomRpc, after(2000).times(1)).invokeRpc(null, null);
 
         //verify the keepalive RPC invoke. Should be never happen.
-        verify(deviceRpc, after(2000).never()).invokeRpc(GetConfig.QNAME, KEEPALIVE_PAYLOAD);
+        verify(deviceDomRpc, after(2000).never()).invokeRpc(GetConfig.QNAME, KEEPALIVE_PAYLOAD);
     }
 
     private static final class LocalNetconfSalFacade implements RemoteDeviceHandler {
@@ -132,7 +136,7 @@ class KeepaliveSalFacadeResponseWaitingTest {
         public void invokeNullRpc() {
             final var local = rpcs;
             if (local != null) {
-                local.invokeRpc(null, null);
+                local.domRpcService().invokeRpc(null, null);
             }
         }
     }
index cc438d0ce1db18e9587120b727b9f88c7e2b93d5..25e88c05a70abbe083ef3943beb4ab43ae2d0b79 100644 (file)
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
 import org.opendaylight.netconf.client.mdsal.NetconfDeviceCommunicator;
 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
@@ -52,6 +53,8 @@ class KeepaliveSalFacadeTest {
     private NetconfDeviceCommunicator listener;
     @Mock
     private Rpcs.Normalized deviceRpc;
+    @Mock
+    private DOMRpcService deviceDomRpc;
 
     private DefaultNetconfTimer timer;
     private KeepaliveSalFacade keepaliveSalFacade;
@@ -62,6 +65,7 @@ class KeepaliveSalFacadeTest {
         timer = new DefaultNetconfTimer();
         keepaliveSalFacade = new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, timer, 1L, 1L);
         keepaliveSalFacade.setListener(listener);
+        doReturn(deviceDomRpc).when(deviceRpc).domRpcService();
     }
 
     @AfterEach
@@ -119,14 +123,14 @@ class KeepaliveSalFacadeTest {
         doAnswer(invocation -> proxyRpc = invocation.getArgument(2, RemoteDeviceServices.class).rpcs())
                 .when(underlyingSalFacade).onDeviceConnected(isNull(), isNull(), any(RemoteDeviceServices.class));
         doReturn(Futures.immediateFailedFuture(new IllegalStateException("illegal-state")))
-                .when(deviceRpc).invokeRpc(any(), any());
+                .when(deviceDomRpc).invokeRpc(any(), any());
 
         keepaliveSalFacade = new KeepaliveSalFacade(REMOTE_DEVICE_ID, underlyingSalFacade, timer, 100L, 1L);
         keepaliveSalFacade.setListener(listener);
 
         keepaliveSalFacade.onDeviceConnected(null, null, new RemoteDeviceServices(deviceRpc, null));
 
-        assertInstanceOf(Rpcs.Normalized.class, proxyRpc)
+        assertInstanceOf(Rpcs.Normalized.class, proxyRpc).domRpcService()
             .invokeRpc(QName.create("foo", "bar"), mock(ContainerNode.class));
 
         verify(listener, times(1)).disconnect();
index 880601e1344f8c080b5dfc48055d4320ca16d600..1b08d398d6468978651a208cd1e2ea2b9acd1d10 100644 (file)
@@ -80,7 +80,7 @@ public class MountInstanceTest {
             notificationService, broker, null);
         verify(mountPointBuilder).addService(eq(DOMSchemaService.class), any());
         verify(mountPointBuilder).addService(DOMDataBroker.class, broker);
-        verify(mountPointBuilder).addService(DOMRpcService.class, rpcService);
+        verify(mountPointBuilder).addService(DOMRpcService.class, rpcService.domRpcService());
         verify(mountPointBuilder).addService(DOMNotificationService.class, notificationService);
     }
 
@@ -90,7 +90,7 @@ public class MountInstanceTest {
             notificationService, null, netconfService);
         verify(mountPointBuilder).addService(eq(DOMSchemaService.class), any());
         verify(mountPointBuilder).addService(NetconfDataTreeService.class, netconfService);
-        verify(mountPointBuilder).addService(DOMRpcService.class, rpcService);
+        verify(mountPointBuilder).addService(DOMRpcService.class, rpcService.domRpcService());
         verify(mountPointBuilder).addService(DOMNotificationService.class, notificationService);
     }
 
index 67b66cc372b76e4f0bcd61947a2e3e568e16b883..1d319e8bf55f3f0834e6d07d89b201f6cf0f518f 100644 (file)
@@ -104,7 +104,8 @@ public class NetconfDeviceRpcTest extends AbstractBaseSchemasTest {
         final RpcResult<NetconfMessage> result = RpcResultBuilder.success(msg).build();
         when(communicatorMock.sendRequest(any(), any())).thenReturn(Futures.immediateFuture(result));
         when(failingTransformer.toRpcResult(any(), any())).thenThrow(new RuntimeException("FAIL"));
-        final NetconfDeviceRpc failingRpc = new NetconfDeviceRpc(SCHEMA_CONTEXT, communicatorMock, failingTransformer);
+        final var failingRpc = new NetconfDeviceRpc(SCHEMA_CONTEXT, communicatorMock, failingTransformer)
+            .domRpcService();
         assertThrows(ExecutionException.class, () -> failingRpc.invokeRpc(type, mock(ContainerNode.class)).get());
         assertThrows(ExecutionException.class, () -> failingRpc.invokeRpc(type, null).get());
     }
@@ -112,7 +113,7 @@ public class NetconfDeviceRpcTest extends AbstractBaseSchemasTest {
     @Test
     public void testInvokeRpc() throws Exception {
         ContainerNode input = createNode("urn:ietf:params:xml:ns:netconf:base:1.0", "2011-06-01", "filter");
-        final DOMRpcResult result = rpc.invokeRpc(type, input).get();
+        final DOMRpcResult result = rpc.domRpcService().invokeRpc(type, input).get();
         assertEquals(expectedReply.value().name(), result.value().name());
         assertEquals(resolveNode(expectedReply), resolveNode(result));
     }
@@ -127,9 +128,9 @@ public class NetconfDeviceRpcTest extends AbstractBaseSchemasTest {
 
     @Test
     public void testRegisterRpcListener() throws Exception {
-        ArgumentCaptor<Collection> argument = ArgumentCaptor.forClass(Collection.class);
+        final var argument = ArgumentCaptor.forClass(Collection.class);
 
-        rpc.registerRpcListener(listener);
+        rpc.domRpcService().registerRpcListener(listener);
 
         verify(listener).onRpcAvailable(argument.capture());
         final Collection<DOMRpcIdentifier> argValue = argument.getValue();
index a36bffcc5c034a1dac5ca6d24a697fb7b9bde7b5..6568ddd7db8f7479fa4b36c76f824ea52565d259 100644 (file)
@@ -69,9 +69,9 @@ public class SchemalessNetconfDeviceRpcTest extends AbstractBaseSchemasTest {
                 + "      </mainroot>\n"
                 + "    </filter>\n"
                 + "  </get-config>"));
-        deviceRpc.invokeRpc(qName, src);
-        ArgumentCaptor<NetconfMessage> msgCaptor = ArgumentCaptor.forClass(NetconfMessage.class);
-        ArgumentCaptor<QName> qnameCaptor = ArgumentCaptor.forClass(QName.class);
+        deviceRpc.schemalessRpcService().invokeRpc(qName, src);
+        final var msgCaptor = ArgumentCaptor.forClass(NetconfMessage.class);
+        final var qnameCaptor = ArgumentCaptor.forClass(QName.class);
         verify(listener).sendRequest(msgCaptor.capture(), qnameCaptor.capture());
         LOG.info(XmlUtil.toString(msgCaptor.getValue().getDocument()));
     }