From 950b212deec041cdf9a9c5669f8fba12806982b9 Mon Sep 17 00:00:00 2001 From: Ruslan Kashapov Date: Mon, 23 Oct 2023 12:38:37 +0300 Subject: [PATCH] Migrate netconf-topology to new transport Deprecated NetconfClientDispatcher was replaced with NetconfClientFactory for further migration. The call-home components were updated only as ancestor component, reflecting api changes. JIRA: NETCONF-1108 Change-Id: Iade4fcaf0680d58319427020d1b1894e3c6ac75e Signed-off-by: Ruslan Kashapov Signed-off-by: Robert Varga --- ...patcher.java => CallHomeMountFactory.java} | 55 ++++--- .../mount/CallHomeMountSessionContext.java | 4 +- .../callhome/mount/CallHomeTopology.java | 10 +- .../IetfZeroTouchCallHomeServerProvider.java | 8 +- ...est.java => CallHomeMountFactoryTest.java} | 19 ++- .../topology/impl/NetconfTopologyImpl.java | 30 ++-- .../impl/NetconfTopologyImplTest.java | 14 +- .../singleton/impl/NetconfNodeContext.java | 2 +- .../impl/NetconfTopologyManager.java | 18 +- .../impl/utils/NetconfTopologySetup.java | 20 +-- .../impl/MountPointEndToEndTest.java | 11 +- .../impl/NetconfTopologyManagerTest.java | 6 +- .../topology/spi/AbstractNetconfTopology.java | 27 +-- ...confClientConfigurationBuilderFactory.java | 8 +- ...ClientConfigurationBuilderFactoryImpl.java | 155 ++++++++++++++++++ .../topology/spi/NetconfNodeHandler.java | 62 ++++--- ...ntConfigurationBuilderFactoryImplTest.java | 137 ++++++++++++++++ .../topology/spi/NetconfNodeHandlerTest.java | 63 ++++--- .../protocol/CallHomeChannelActivator.java | 4 +- .../protocol/CallHomeSessionContext.java | 21 ++- .../tls/CallHomeTlsSessionContext.java | 19 ++- 21 files changed, 514 insertions(+), 179 deletions(-) rename apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/{CallHomeMountDispatcher.java => CallHomeMountFactory.java} (74%) rename apps/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/{CallHomeMountDispatcherTest.java => CallHomeMountFactoryTest.java} (93%) create mode 100644 apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImpl.java create mode 100644 apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImplTest.java diff --git a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcher.java b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountFactory.java similarity index 74% rename from apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcher.java rename to apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountFactory.java index c0d9399735..e9933ea886 100644 --- a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcher.java +++ b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountFactory.java @@ -10,9 +10,9 @@ package org.opendaylight.netconf.callhome.mount; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.FailedFuture; -import io.netty.util.concurrent.Future; import java.net.InetSocketAddress; import org.opendaylight.controller.config.threadpool.ScheduledThreadPool; import org.opendaylight.controller.config.threadpool.ThreadPool; @@ -21,7 +21,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.netconf.callhome.protocol.CallHomeChannelActivator; import org.opendaylight.netconf.callhome.protocol.CallHomeNetconfSubsystemListener; import org.opendaylight.netconf.callhome.protocol.CallHomeProtocolSessionContext; -import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.client.NetconfClientFactory; import org.opendaylight.netconf.client.NetconfClientSession; import org.opendaylight.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas; @@ -36,16 +36,16 @@ import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@Component(service = { CallHomeMountDispatcher.class, CallHomeNetconfSubsystemListener.class }, immediate = true) +@Component(service = { CallHomeMountFactory.class, CallHomeNetconfSubsystemListener.class }, immediate = true) // Non-final for testing -public class CallHomeMountDispatcher implements NetconfClientDispatcher, CallHomeNetconfSubsystemListener { - private static final Logger LOG = LoggerFactory.getLogger(CallHomeMountDispatcher.class); +public class CallHomeMountFactory implements NetconfClientFactory, CallHomeNetconfSubsystemListener { + private static final Logger LOG = LoggerFactory.getLogger(CallHomeMountFactory.class); private final CallHomeMountSessionManager sessionManager = new CallHomeMountSessionManager(); private final String topologyId; private final EventExecutor eventExecutor; - private final ScheduledThreadPool keepaliveExecutor; - private final ThreadPool processingExecutor; + private final ScheduledThreadPool scheduledThreadPool; + private final ThreadPool processingThreadPool; private final SchemaResourceManager schemaRepositoryProvider; private final DataBroker dataBroker; private final DOMMountPointService mountService; @@ -57,40 +57,40 @@ public class CallHomeMountDispatcher implements NetconfClientDispatcher, CallHom private final BaseNetconfSchemas baseSchemas; - public CallHomeMountDispatcher(final String topologyId, final EventExecutor eventExecutor, - final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor, + public CallHomeMountFactory(final String topologyId, final EventExecutor eventExecutor, + final ScheduledThreadPool scheduledThreadPool, final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider, final BaseNetconfSchemas baseSchemas, final DataBroker dataBroker, final DOMMountPointService mountService, final NetconfClientConfigurationBuilderFactory builderFactory) { - this(topologyId, eventExecutor, keepaliveExecutor, processingExecutor, schemaRepositoryProvider, baseSchemas, - dataBroker, mountService, builderFactory, null); + this(topologyId, eventExecutor, scheduledThreadPool, processingThreadPool, schemaRepositoryProvider, + baseSchemas, dataBroker, mountService, builderFactory, null); } @Activate - public CallHomeMountDispatcher( + public CallHomeMountFactory( @Reference(target = "(type=global-event-executor)") final EventExecutor eventExecutor, @Reference(target = "(type=global-netconf-ssh-scheduled-executor)") - final ScheduledThreadPool keepaliveExecutor, - @Reference(target = "(type=global-netconf-processing-executor)") final ThreadPool processingExecutor, + final ScheduledThreadPool scheduledThreadPool, + @Reference(target = "(type=global-netconf-processing-executor)") final ThreadPool processingThreadPool, @Reference final SchemaResourceManager schemaRepositoryProvider, @Reference final BaseNetconfSchemas baseSchemas, @Reference final DataBroker dataBroker, @Reference final DOMMountPointService mountService, - @Reference final NetconfClientConfigurationBuilderFactory builderFactory, + @Reference(target = "(type=legacy)") final NetconfClientConfigurationBuilderFactory builderFactory, @Reference final DeviceActionFactory deviceActionFactory) { - this(NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, eventExecutor, keepaliveExecutor, processingExecutor, + this(NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, eventExecutor, scheduledThreadPool, processingThreadPool, schemaRepositoryProvider, baseSchemas, dataBroker, mountService, builderFactory, deviceActionFactory); } - public CallHomeMountDispatcher(final String topologyId, final EventExecutor eventExecutor, - final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor, + public CallHomeMountFactory(final String topologyId, final EventExecutor eventExecutor, + final ScheduledThreadPool scheduledThreadPool, final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider, final BaseNetconfSchemas baseSchemas, final DataBroker dataBroker, final DOMMountPointService mountService, final NetconfClientConfigurationBuilderFactory builderFactory, final DeviceActionFactory deviceActionFactory) { this.topologyId = topologyId; this.eventExecutor = eventExecutor; - this.keepaliveExecutor = keepaliveExecutor; - this.processingExecutor = processingExecutor; + this.scheduledThreadPool = scheduledThreadPool; + this.processingThreadPool = processingThreadPool; this.schemaRepositoryProvider = schemaRepositoryProvider; this.deviceActionFactory = deviceActionFactory; this.baseSchemas = requireNonNull(baseSchemas); @@ -101,15 +101,15 @@ public class CallHomeMountDispatcher implements NetconfClientDispatcher, CallHom @Deprecated @Override - public Future createClient(final NetconfClientConfiguration clientConfiguration) { + public ListenableFuture createClient(final NetconfClientConfiguration clientConfiguration) { return activateChannel(clientConfiguration); } - private Future activateChannel(final NetconfClientConfiguration conf) { + private ListenableFuture activateChannel(final NetconfClientConfiguration conf) { final InetSocketAddress remoteAddr = conf.getAddress(); final CallHomeMountSessionContext context = sessionManager().getByAddress(remoteAddr); LOG.info("Activating NETCONF channel for ip {} device context {}", remoteAddr, context); - return context == null ? new FailedFuture<>(eventExecutor, new NullPointerException()) + return context == null ? Futures.immediateFailedFuture(new NullPointerException("context is null")) : context.activateNetconfChannel(conf.getSessionListener()); } @@ -130,7 +130,7 @@ public class CallHomeMountDispatcher implements NetconfClientDispatcher, CallHom @VisibleForTesting void createTopology() { - topology = new CallHomeTopology(topologyId, this, eventExecutor, keepaliveExecutor, processingExecutor, + topology = new CallHomeTopology(topologyId, this, eventExecutor, scheduledThreadPool, processingThreadPool, schemaRepositoryProvider, dataBroker, mountService, builderFactory, baseSchemas, deviceActionFactory); } @@ -138,4 +138,9 @@ public class CallHomeMountDispatcher implements NetconfClientDispatcher, CallHom CallHomeMountSessionManager sessionManager() { return sessionManager; } + + @Override + public void close() throws Exception { + // no action + } } diff --git a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContext.java b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContext.java index d0c5b604c6..52c094bc1c 100644 --- a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContext.java +++ b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContext.java @@ -10,7 +10,7 @@ package org.opendaylight.netconf.callhome.mount; import static java.util.Objects.requireNonNull; import com.google.common.base.MoreObjects; -import io.netty.util.concurrent.Future; +import com.google.common.util.concurrent.ListenableFuture; import org.opendaylight.netconf.api.NetconfTerminationReason; import org.opendaylight.netconf.api.messages.NetconfMessage; import org.opendaylight.netconf.callhome.protocol.CallHomeChannelActivator; @@ -107,7 +107,7 @@ class CallHomeMountSessionContext { .build(); } - Future activateNetconfChannel(final NetconfClientSessionListener sessionListener) { + ListenableFuture activateNetconfChannel(final NetconfClientSessionListener sessionListener) { return activator.activate(wrap(sessionListener)); } diff --git a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeTopology.java b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeTopology.java index a308e9789d..f85a007884 100644 --- a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeTopology.java +++ b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeTopology.java @@ -13,7 +13,7 @@ import org.opendaylight.controller.config.threadpool.ScheduledThreadPool; import org.opendaylight.controller.config.threadpool.ThreadPool; import org.opendaylight.mdsal.binding.api.DataBroker; import org.opendaylight.mdsal.dom.api.DOMMountPointService; -import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.client.NetconfClientFactory; import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas; import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory; import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager; @@ -24,13 +24,13 @@ import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology. // Non-final for mocking public class CallHomeTopology extends AbstractNetconfTopology { - public CallHomeTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher, - final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, - final ThreadPool processingExecutor, final SchemaResourceManager schemaRepositoryProvider, + public CallHomeTopology(final String topologyId, final NetconfClientFactory clientFactory, + final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool, + final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider, final DataBroker dataBroker, final DOMMountPointService mountPointService, final NetconfClientConfigurationBuilderFactory builderFactory, final BaseNetconfSchemas baseSchemas, final DeviceActionFactory deviceActionFactory) { - super(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor, + super(topologyId, clientFactory, eventExecutor, scheduledThreadPool, processingThreadPool, schemaRepositoryProvider, dataBroker, mountPointService, builderFactory, deviceActionFactory, baseSchemas); } diff --git a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/IetfZeroTouchCallHomeServerProvider.java b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/IetfZeroTouchCallHomeServerProvider.java index bb1b327915..8134a15012 100644 --- a/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/IetfZeroTouchCallHomeServerProvider.java +++ b/apps/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/IetfZeroTouchCallHomeServerProvider.java @@ -65,7 +65,7 @@ public final class IetfZeroTouchCallHomeServerProvider private static final Logger LOG = LoggerFactory.getLogger(IetfZeroTouchCallHomeServerProvider.class); private final DataBroker dataBroker; - private final CallHomeMountDispatcher mountDispacher; + private final CallHomeMountFactory mountDispacher; private final CallHomeAuthProviderImpl authProvider; private final CallhomeStatusReporter statusReporter; private final int port; @@ -75,15 +75,15 @@ public final class IetfZeroTouchCallHomeServerProvider @Activate public IetfZeroTouchCallHomeServerProvider(@Reference final DataBroker dataBroker, - @Reference final CallHomeMountDispatcher mountDispacher) { + @Reference final CallHomeMountFactory mountDispacher) { // FIXME: make this configurable this(dataBroker, mountDispacher, Uint16.valueOf(4334)); } public IetfZeroTouchCallHomeServerProvider(final DataBroker dataBroker, - final CallHomeMountDispatcher mountDispacher, final Uint16 port) { + final CallHomeMountFactory mountFactory, final Uint16 port) { this.dataBroker = requireNonNull(dataBroker); - this.mountDispacher = requireNonNull(mountDispacher); + this.mountDispacher = requireNonNull(mountFactory); LOG.info("Setting port for call home server to {}", port); this.port = port.toJava(); diff --git a/apps/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcherTest.java b/apps/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountFactoryTest.java similarity index 93% rename from apps/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcherTest.java rename to apps/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountFactoryTest.java index d3c3c4d375..52746952d3 100644 --- a/apps/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountDispatcherTest.java +++ b/apps/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountFactoryTest.java @@ -7,7 +7,8 @@ */ package org.opendaylight.netconf.callhome.mount; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; @@ -16,9 +17,9 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.Future; import java.net.InetSocketAddress; import java.net.UnknownHostException; +import java.util.concurrent.ExecutionException; import org.junit.Before; import org.junit.Test; import org.opendaylight.controller.config.threadpool.ScheduledThreadPool; @@ -28,7 +29,6 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.netconf.api.messages.NetconfHelloMessageAdditionalHeader; import org.opendaylight.netconf.callhome.protocol.CallHomeChannelActivator; import org.opendaylight.netconf.callhome.protocol.CallHomeProtocolSessionContext; -import org.opendaylight.netconf.client.NetconfClientSession; import org.opendaylight.netconf.client.NetconfClientSessionListener; import org.opendaylight.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder; @@ -39,14 +39,14 @@ import org.opendaylight.netconf.topology.spi.NetconfClientConfigurationBuilderFa import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node; -public class CallHomeMountDispatcherTest { +public class CallHomeMountFactoryTest { private String topologyId; private EventExecutor mockExecutor; private ScheduledThreadPool mockKeepAlive; private ThreadPool mockProcessingExecutor; private SchemaResourceManager mockSchemaRepoProvider; - private CallHomeMountDispatcher instance; + private CallHomeMountFactory instance; private DataBroker mockDataBroker; private DOMMountPointService mockMount; @@ -71,7 +71,7 @@ public class CallHomeMountDispatcherTest { mockBuilderFactory = mock(NetconfClientConfigurationBuilderFactory .class); mockBaseSchemas = mock(BaseNetconfSchemas.class); - instance = new CallHomeMountDispatcher(topologyId, mockExecutor, mockKeepAlive, + instance = new CallHomeMountFactory(topologyId, mockExecutor, mockKeepAlive, mockProcessingExecutor, mockSchemaRepoProvider, mockBaseSchemas, mockDataBroker, mockMount, mockBuilderFactory) { @Override @@ -115,14 +115,15 @@ public class CallHomeMountDispatcherTest { } @Test - public void noSessionIsCreatedWithoutAContextAvailableForAGivenAddress() { + public void noSessionIsCreatedWithoutAContextAvailableForAGivenAddress() throws Exception { // given final InetSocketAddress someAddress = InetSocketAddress.createUnresolved("1.2.3.4", 123); final NetconfClientConfiguration someCfg = someConfiguration(someAddress); // when - final Future future = instance.createClient(someCfg); + final var future = instance.createClient(someCfg); // then - assertFalse(future.isSuccess()); + assertNotNull(future); + assertThrows(ExecutionException.class, future::get); } @Test diff --git a/apps/netconf-topology-impl/src/main/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImpl.java b/apps/netconf-topology-impl/src/main/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImpl.java index b101975f91..0484dd7c9f 100644 --- a/apps/netconf-topology-impl/src/main/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImpl.java +++ b/apps/netconf-topology-impl/src/main/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImpl.java @@ -23,7 +23,7 @@ import org.opendaylight.mdsal.binding.api.DataTreeModification; import org.opendaylight.mdsal.binding.api.RpcProviderService; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMMountPointService; -import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.client.NetconfClientFactory; import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas; import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory; import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager; @@ -58,43 +58,43 @@ public class NetconfTopologyImpl extends AbstractNetconfTopology @Inject @Activate public NetconfTopologyImpl( - @Reference(target = "(type=netconf-client-dispatcher)") final NetconfClientDispatcher clientDispatcher, + @Reference(target = "(type=netconf-client-factory)") final NetconfClientFactory clientFactory, @Reference(target = "(type=global-event-executor)") final EventExecutor eventExecutor, @Reference(target = "(type=global-netconf-ssh-scheduled-executor)") - final ScheduledThreadPool keepaliveExecutor, - @Reference(target = "(type=global-netconf-processing-executor)") final ThreadPool processingExecutor, + final ScheduledThreadPool scheduledThreadPool, + @Reference(target = "(type=global-netconf-processing-executor)") final ThreadPool processingThreadPool, @Reference final SchemaResourceManager schemaRepositoryProvider, @Reference final DataBroker dataBroker, @Reference final DOMMountPointService mountPointService, @Reference final AAAEncryptionService encryptionService, - @Reference final NetconfClientConfigurationBuilderFactory builderFactory, + @Reference(target = "(type=default)") final NetconfClientConfigurationBuilderFactory builderFactory, @Reference final RpcProviderService rpcProviderService, @Reference final BaseNetconfSchemas baseSchemas, @Reference final DeviceActionFactory deviceActionFactory) { - this(NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, clientDispatcher, eventExecutor, keepaliveExecutor, - processingExecutor, schemaRepositoryProvider, dataBroker, mountPointService, encryptionService, + this(NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, clientFactory, eventExecutor, scheduledThreadPool, + processingThreadPool, schemaRepositoryProvider, dataBroker, mountPointService, encryptionService, builderFactory, rpcProviderService, baseSchemas, deviceActionFactory); } - public NetconfTopologyImpl(final String topologyId, final NetconfClientDispatcher clientDispatcher, - final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, - final ThreadPool processingExecutor, final SchemaResourceManager schemaRepositoryProvider, + public NetconfTopologyImpl(final String topologyId, final NetconfClientFactory clientclientFactory, + final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool, + final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider, final DataBroker dataBroker, final DOMMountPointService mountPointService, final AAAEncryptionService encryptionService, final NetconfClientConfigurationBuilderFactory builderFactory, final RpcProviderService rpcProviderService, final BaseNetconfSchemas baseSchemas) { - this(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor, + this(topologyId, clientclientFactory, eventExecutor, scheduledThreadPool, processingThreadPool, schemaRepositoryProvider, dataBroker, mountPointService, encryptionService, builderFactory, rpcProviderService, baseSchemas, null); } @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR", justification = "DTCL registration of 'this'") - public NetconfTopologyImpl(final String topologyId, final NetconfClientDispatcher clientDispatcher, - final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, - final ThreadPool processingExecutor, final SchemaResourceManager schemaRepositoryProvider, + public NetconfTopologyImpl(final String topologyId, final NetconfClientFactory clientFactory, + final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool, + final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider, final DataBroker dataBroker, final DOMMountPointService mountPointService, final AAAEncryptionService encryptionService, final NetconfClientConfigurationBuilderFactory builderFactory, final RpcProviderService rpcProviderService, final BaseNetconfSchemas baseSchemas, final DeviceActionFactory deviceActionFactory) { - super(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor, + super(topologyId, clientFactory, eventExecutor, scheduledThreadPool, processingThreadPool, schemaRepositoryProvider, dataBroker, mountPointService, builderFactory, deviceActionFactory, baseSchemas); LOG.debug("Registering datastore listener"); diff --git a/apps/netconf-topology-impl/src/test/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImplTest.java b/apps/netconf-topology-impl/src/test/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImplTest.java index a786462a42..0b88185c61 100644 --- a/apps/netconf-topology-impl/src/test/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImplTest.java +++ b/apps/netconf-topology-impl/src/test/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImplTest.java @@ -31,7 +31,7 @@ import org.opendaylight.mdsal.binding.api.WriteTransaction; import org.opendaylight.mdsal.common.api.CommitInfo; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMMountPointService; -import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.client.NetconfClientFactory; import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas; import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager; import org.opendaylight.netconf.client.mdsal.impl.DefaultBaseNetconfSchemas; @@ -65,7 +65,7 @@ class NetconfTopologyImplTest { InstanceIdentifier.builder(NetworkTopology.class).child(Topology.class, TOPOLOGY_KEY).build(); @Mock - private NetconfClientDispatcher mockedClientDispatcher; + private NetconfClientFactory mockedClientFactory; @Mock private EventExecutor mockedEventExecutor; @Mock @@ -100,7 +100,7 @@ class NetconfTopologyImplTest { doReturn(wtx).when(dataBroker).newWriteOnlyTransaction(); doReturn(CommitInfo.emptyFluentFuture()).when(wtx).commit(); - topology = new TestingNetconfTopologyImpl(TOPOLOGY_KEY.getTopologyId().getValue(), mockedClientDispatcher, + topology = new TestingNetconfTopologyImpl(TOPOLOGY_KEY.getTopologyId().getValue(), mockedClientFactory, mockedEventExecutor, mockedKeepaliveExecutor, mockedProcessingExecutor, mockedResourceManager, dataBroker, mountPointService, encryptionService, builderFactory, rpcProviderService, new DefaultBaseNetconfSchemas(new DefaultYangParserFactory())); @@ -154,14 +154,14 @@ class NetconfTopologyImplTest { } private static class TestingNetconfTopologyImpl extends NetconfTopologyImpl { - TestingNetconfTopologyImpl(final String topologyId, final NetconfClientDispatcher clientDispatcher, - final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, - final ThreadPool processingExecutor, final SchemaResourceManager schemaRepositoryProvider, + TestingNetconfTopologyImpl(final String topologyId, final NetconfClientFactory clientFactory, + final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool, + final ThreadPool processingThreadPool, final SchemaResourceManager schemaRepositoryProvider, final DataBroker dataBroker, final DOMMountPointService mountPointService, final AAAEncryptionService encryptionService, final NetconfClientConfigurationBuilderFactory builderFactory, final RpcProviderService rpcProviderService, final BaseNetconfSchemas baseSchemas) { - super(topologyId, clientDispatcher, eventExecutor, keepaliveExecutor, processingExecutor, + super(topologyId, clientFactory, eventExecutor, scheduledThreadPool, processingThreadPool, schemaRepositoryProvider, dataBroker, mountPointService, encryptionService, builderFactory, rpcProviderService, baseSchemas); } diff --git a/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeContext.java b/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeContext.java index 19f360030d..dd6a607a58 100644 --- a/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeContext.java +++ b/apps/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/NetconfNodeContext.java @@ -141,7 +141,7 @@ final class NetconfNodeContext implements AutoCloseable { // Instantiate the handler ... masterSalFacade = createSalFacade(netconfNode.requireLockDatastore()); - nodeHandler = new NetconfNodeHandler(setup.getNetconfClientDispatcher(), setup.getEventExecutor(), + nodeHandler = new NetconfNodeHandler(setup.getNetconfClientFactory(), setup.getKeepaliveExecutor(), setup.getBaseSchemas(), schemaManager, setup.getProcessingExecutor(), builderFactory, deviceActionFactory, masterSalFacade, remoteDeviceId, configNode.getNodeId(), netconfNode, nodeOptional); 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 d80b6f7ec5..d85e280692 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 @@ -42,7 +42,7 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService; import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider; import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration; import org.opendaylight.mdsal.singleton.common.api.ServiceGroupIdentifier; -import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.client.NetconfClientFactory; import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas; import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory; import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId; @@ -103,7 +103,7 @@ public class NetconfTopologyManager implements ClusteredDataTreeChangeListener activeConnectors = new HashMap<>(); - private final NetconfClientDispatcher clientDispatcher; + private final NetconfClientFactory clientFactory; private final EventExecutor eventExecutor; private final DeviceActionFactory deviceActionFactory; private final SchemaResourceManager schemaManager; private final BaseNetconfSchemas baseSchemas; private final NetconfClientConfigurationBuilderFactory builderFactory; - protected final ScheduledExecutorService keepaliveExecutor; + protected final ScheduledExecutorService scheduledExecutor; protected final Executor processingExecutor; protected final DataBroker dataBroker; protected final DOMMountPointService mountPointService; protected final String topologyId; - protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher, - final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, - final ThreadPool processingExecutor, final SchemaResourceManager schemaManager, final DataBroker dataBroker, - final DOMMountPointService mountPointService, final NetconfClientConfigurationBuilderFactory builderFactory, + protected AbstractNetconfTopology(final String topologyId, final NetconfClientFactory clientDispatcher, + final EventExecutor eventExecutor, final ScheduledThreadPool scheduledThreadPool, + final ThreadPool processingThreadPool, final SchemaResourceManager schemaManager, + final DataBroker dataBroker, final DOMMountPointService mountPointService, + final NetconfClientConfigurationBuilderFactory builderFactory, final DeviceActionFactory deviceActionFactory, final BaseNetconfSchemas baseSchemas) { this.topologyId = requireNonNull(topologyId); - this.clientDispatcher = clientDispatcher; + this.clientFactory = clientDispatcher; this.eventExecutor = eventExecutor; - this.keepaliveExecutor = keepaliveExecutor.getExecutor(); - this.processingExecutor = processingExecutor.getExecutor(); + this.scheduledExecutor = scheduledThreadPool.getExecutor(); + this.processingExecutor = processingThreadPool.getExecutor(); this.schemaManager = requireNonNull(schemaManager); this.deviceActionFactory = deviceActionFactory; this.dataBroker = requireNonNull(dataBroker); @@ -124,9 +125,9 @@ public abstract class AbstractNetconfTopology { final NetconfNodeHandler nodeHandler; try { - nodeHandler = new NetconfNodeHandler(clientDispatcher, eventExecutor, keepaliveExecutor, baseSchemas, - schemaManager, processingExecutor, builderFactory, deviceActionFactory, deviceSalFacade, deviceId, - nodeId, netconfNode, nodeOptional); + nodeHandler = new NetconfNodeHandler(clientFactory, scheduledExecutor, baseSchemas, + schemaManager, processingExecutor, builderFactory, deviceActionFactory, deviceSalFacade, + deviceId, nodeId, netconfNode, nodeOptional); } catch (IllegalArgumentException e) { // This is a workaround for NETCONF-1114 where the encrypted password's lexical structure is not enforced // in the datastore and it ends up surfacing when we decrypt the password. diff --git a/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/DefaultNetconfClientConfigurationBuilderFactory.java b/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/DefaultNetconfClientConfigurationBuilderFactory.java index edf74fc3d5..9ebaa1a5a0 100644 --- a/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/DefaultNetconfClientConfigurationBuilderFactory.java +++ b/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/DefaultNetconfClientConfigurationBuilderFactory.java @@ -33,10 +33,14 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; /** - * Default implementation of NetconfClientConfigurationBuildFactory. + * Legacy implementation of NetconfClientConfigurationBuildFactory. + * + * @deprecated as outdated. Should be replaced with {@link NetconfClientConfigurationBuilderFactoryImpl} once + * callhome-provider is migrated to transport-api. */ -@Component +@Component(service = NetconfClientConfigurationBuilderFactory.class, property = "type=legacy") @Singleton +@Deprecated(forRemoval = true) public final class DefaultNetconfClientConfigurationBuilderFactory implements NetconfClientConfigurationBuilderFactory { private final SslHandlerFactoryProvider sslHandlerFactoryProvider; private final AAAEncryptionService encryptionService; diff --git a/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImpl.java b/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImpl.java new file mode 100644 index 0000000000..72ff522d1c --- /dev/null +++ b/apps/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImpl.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 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.topology.spi; + +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Strings; +import java.io.IOException; +import java.io.StringReader; +import java.security.KeyPair; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.opendaylight.aaa.encrypt.AAAEncryptionService; +import org.opendaylight.aaa.encrypt.PKIUtil; +import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol; +import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder; +import org.opendaylight.netconf.client.mdsal.api.CredentialProvider; +import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider; +import org.opendaylight.netconf.shaded.sshd.client.auth.pubkey.UserAuthPublicKeyFactory; +import org.opendaylight.netconf.shaded.sshd.common.keyprovider.KeyIdentityProvider; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.password.grouping.password.type.CleartextPasswordBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.SshClientParametersBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.TcpClientParametersBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.ClientIdentity; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.ClientIdentityBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.client.identity.PasswordBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.connection.parameters.Protocol.Name; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.Credentials; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.KeyAuth; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.LoginPw; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.LoginPwUnencrypted; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * Default implementation of NetconfClientConfigurationBuildFactory. + */ +@Component(service = NetconfClientConfigurationBuilderFactory.class, property = "type=default") +@Singleton +public final class NetconfClientConfigurationBuilderFactoryImpl implements NetconfClientConfigurationBuilderFactory { + private final SslHandlerFactoryProvider sslHandlerFactoryProvider; + private final AAAEncryptionService encryptionService; + private final CredentialProvider credentialProvider; + + @Inject + @Activate + public NetconfClientConfigurationBuilderFactoryImpl( + @Reference final AAAEncryptionService encryptionService, + @Reference final CredentialProvider credentialProvider, + @Reference final SslHandlerFactoryProvider sslHandlerFactoryProvider) { + this.encryptionService = requireNonNull(encryptionService); + this.credentialProvider = requireNonNull(credentialProvider); + this.sslHandlerFactoryProvider = requireNonNull(sslHandlerFactoryProvider); + } + + @Override + public NetconfClientConfigurationBuilder createClientConfigurationBuilder(final NodeId nodeId, + final NetconfNode node) { + final var builder = NetconfClientConfigurationBuilder.create(); + final var protocol = node.getProtocol(); + if (node.requireTcpOnly()) { + builder.withProtocol(NetconfClientProtocol.TCP); + } else if (protocol == null || protocol.getName() == Name.SSH) { + builder.withProtocol(NetconfClientProtocol.SSH); + setSshParametersFromCredentials(builder, node.getCredentials()); + } else if (protocol.getName() == Name.TLS) { + builder.withProtocol(NetconfClientProtocol.TLS).withTransportSslHandlerFactory(channel -> { + final var sslHandlerBuilder = + sslHandlerFactoryProvider.getSslHandlerFactory(protocol.getSpecification()); + return sslHandlerBuilder.createSslHandler(); + }); + } else { + throw new IllegalArgumentException("Unsupported protocol type: " + protocol.getName()); + } + + final var helloCapabilities = node.getOdlHelloMessageCapabilities(); + if (helloCapabilities != null) { + builder.withOdlHelloCapabilities(List.copyOf(helloCapabilities.requireCapability())); + } + + return builder + .withName(nodeId.getValue()) + .withTcpParameters(new TcpClientParametersBuilder() + .setRemoteAddress(node.requireHost()) + .setRemotePort(node.requirePort()).build()) + .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava()); + } + + private void setSshParametersFromCredentials(final NetconfClientConfigurationBuilder confBuilder, + final Credentials credentials) { + final var sshParamsBuilder = new SshClientParametersBuilder(); + if (credentials instanceof LoginPwUnencrypted unencrypted) { + final var loginPassword = unencrypted.getLoginPasswordUnencrypted(); + sshParamsBuilder.setClientIdentity(loginPasswordIdentity( + loginPassword.getUsername(), loginPassword.getPassword())); + + } else if (credentials instanceof LoginPw loginPw) { + final var loginPassword = loginPw.getLoginPassword(); + sshParamsBuilder.setClientIdentity(loginPasswordIdentity( + loginPassword.getUsername(), encryptionService.decrypt(loginPassword.getPassword()))); + + } else if (credentials instanceof KeyAuth keyAuth) { + final var keyBased = keyAuth.getKeyBased(); + sshParamsBuilder.setClientIdentity(new ClientIdentityBuilder().setUsername(keyBased.getUsername()).build()); + confBuilder.withSshConfigurator(factoryMgr -> { + final var keyPair = getKeyPair(keyBased.getKeyId(), credentialProvider, encryptionService); + factoryMgr.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keyPair)); + final var factory = new UserAuthPublicKeyFactory(); + factory.setSignatureFactories(factoryMgr.getSignatureFactories()); + factoryMgr.setUserAuthFactories(List.of(factory)); + }); + } else { + throw new IllegalArgumentException("Unsupported credential type: " + credentials.getClass()); + } + confBuilder.withSshParameters(sshParamsBuilder.build()); + } + + private static ClientIdentity loginPasswordIdentity(final String username, final String password) { + requireNonNull(username, "username is undefined"); + requireNonNull(password, "password is undefined"); + return new ClientIdentityBuilder() + .setUsername(username) + .setPassword(new PasswordBuilder() + .setPasswordType(new CleartextPasswordBuilder().setCleartextPassword(password).build()) + .build()) + .build(); + } + + private static KeyPair getKeyPair(final String keyId, + final CredentialProvider credentialProvider, final AAAEncryptionService encryptionService) { + + // public key retrieval logic taken from DatastoreBackedPublicKeyAuth + final var dsKeypair = credentialProvider.credentialForId(keyId); + if (dsKeypair == null) { + throw new IllegalArgumentException("No keypair found with keyId=" + keyId); + } + final var passPhrase = Strings.isNullOrEmpty(dsKeypair.getPassphrase()) ? "" : dsKeypair.getPassphrase(); + try { + return new PKIUtil().decodePrivateKey( + new StringReader(encryptionService.decrypt(dsKeypair.getPrivateKey()).replace("\\n", "\n")), + encryptionService.decrypt(passPhrase)); + } catch (IOException e) { + throw new IllegalStateException("Could not decode private key with keyId=" + keyId, e); + } + } +} 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 47f980c660..2e0de08a1c 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 @@ -10,11 +10,12 @@ package org.opendaylight.netconf.topology.spi; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; -import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.Future; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -22,7 +23,7 @@ import org.checkerframework.checker.lock.qual.GuardedBy; import org.checkerframework.checker.lock.qual.Holding; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.mdsal.dom.api.DOMNotification; -import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.client.NetconfClientFactory; import org.opendaylight.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.netconf.client.mdsal.LibraryModulesSchemas; import org.opendaylight.netconf.client.mdsal.LibrarySchemaSourceProvider; @@ -40,6 +41,7 @@ import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId; import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices; import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager; import org.opendaylight.netconf.client.mdsal.spi.KeepaliveSalFacade; +import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev221225.NetconfNodeAugmentedOptional; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode; @@ -58,11 +60,11 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re private static final Logger LOG = LoggerFactory.getLogger(NetconfNodeHandler.class); private final @NonNull List> yanglibRegistrations; - private final @NonNull NetconfClientDispatcher clientDispatcher; + private final @NonNull NetconfClientFactory clientFactory; private final @NonNull NetconfClientConfiguration clientConfig; private final @NonNull NetconfDeviceCommunicator communicator; private final @NonNull RemoteDeviceHandler delegate; - private final @NonNull EventExecutor eventExecutor; + private final @NonNull ListeningScheduledExecutorService scheduledExecutor; private final @NonNull RemoteDeviceId deviceId; private final long maxAttempts; @@ -74,17 +76,18 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re @GuardedBy("this") private long lastSleep; @GuardedBy("this") - private Future currentTask; + private ListenableFuture currentTask; - public NetconfNodeHandler(final NetconfClientDispatcher clientDispatcher, final EventExecutor eventExecutor, - final ScheduledExecutorService keepaliveExecutor, final BaseNetconfSchemas baseSchemas, + public NetconfNodeHandler(final NetconfClientFactory clientFactory, + final ScheduledExecutorService scheduledExecutor, final BaseNetconfSchemas baseSchemas, final SchemaResourceManager schemaManager, final Executor processingExecutor, final NetconfClientConfigurationBuilderFactory builderFactory, final DeviceActionFactory deviceActionFactory, final RemoteDeviceHandler delegate, final RemoteDeviceId deviceId, final NodeId nodeId, final NetconfNode node, final NetconfNodeAugmentedOptional nodeOptional) { - this.clientDispatcher = requireNonNull(clientDispatcher); - this.eventExecutor = requireNonNull(eventExecutor); + this.clientFactory = requireNonNull(clientFactory); + // FIXME: do not wrap this executor + this.scheduledExecutor = MoreExecutors.listeningDecorator(scheduledExecutor); this.delegate = requireNonNull(delegate); this.deviceId = requireNonNull(deviceId); @@ -104,7 +107,7 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re final long keepaliveDelay = node.requireKeepaliveDelay().toJava(); if (keepaliveDelay > 0) { LOG.info("Adding keepalive facade, for device {}", nodeId); - salFacade = keepAliveFacade = new KeepaliveSalFacade(deviceId, this, keepaliveExecutor, keepaliveDelay, + salFacade = keepAliveFacade = new KeepaliveSalFacade(deviceId, this, scheduledExecutor, keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava()); } else { salFacade = this; @@ -154,11 +157,16 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re @Holding("this") private void lockedConnect() { - currentTask = clientDispatcher.createClient(clientConfig); - currentTask.addListener(this::connectComplete); + try { + final var clientFuture = clientFactory.createClient(clientConfig); + clientFuture.addListener(() -> connectComplete(clientFuture), MoreExecutors.directExecutor()); + currentTask = clientFuture; + } catch (UnsupportedConfigurationException e) { + onDeviceFailed(e); + } } - private void connectComplete(final Future future) { + private void connectComplete(final ListenableFuture future) { // Locked manipulation of internal state synchronized (this) { // A quick sanity check @@ -166,16 +174,18 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re LOG.warn("Ignoring connection completion, expected {} actual {}", future, currentTask); return; } - currentTask = null; - final var cause = future.cause(); - if (cause == null || cause instanceof CancellationException) { - // Success or cancellation, nothing else to do. - // In case of success the rest of the setup is driven by RemoteDeviceHandler callbacks - return; + // ListenableFuture provide no detail on error unless you attempt to get() the result + // then only the original exception is rethrown wrapped with ExecutionException + try { + if (future.isCancelled() || future.isDone() && future.get() != null) { + // Success or cancellation, nothing else to do. + // In case of success the rest of the setup is driven by RemoteDeviceHandler callbacks + return; + } + } catch (InterruptedException | ExecutionException e) { + LOG.debug("Connection attempt {} to {} failed", attempts, deviceId, e); } - - LOG.debug("Connection attempt {} to {} failed", attempts, deviceId, cause); } // We are invoking callbacks, do not hold locks @@ -257,9 +267,9 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re // Schedule a task for the right time. We always go through the executor to eliminate the special case of // immediate reconnect. While we could check and got to lockedConnect(), it makes for a rare special case. - // That special case makes for more code paths to test and introduces additional uncertainty as to whether - // the attempt was executed on on this thread or not. - currentTask = eventExecutor.schedule(this::reconnect, delayMillis, TimeUnit.MILLISECONDS); + // That special case makes for more code paths to test and introduces additional uncertainty whether + // the attempt was executed on this thread or not. + currentTask = scheduledExecutor.schedule(this::reconnect, delayMillis, TimeUnit.MILLISECONDS); return null; } diff --git a/apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImplTest.java b/apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImplTest.java new file mode 100644 index 0000000000..2daec7d2e3 --- /dev/null +++ b/apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfClientConfigurationBuilderFactoryImplTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2023 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.topology.spi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.NoSuchElementException; +import org.junit.jupiter.api.BeforeEach; +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.aaa.encrypt.AAAEncryptionService; +import org.opendaylight.netconf.client.NetconfClientSessionListener; +import org.opendaylight.netconf.client.conf.NetconfClientConfiguration; +import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol; +import org.opendaylight.netconf.client.mdsal.api.CredentialProvider; +import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.connection.parameters.Protocol.Name; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.connection.parameters.ProtocolBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.LoginPwUnencryptedBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencryptedBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNodeBuilder; +import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId; +import org.opendaylight.yangtools.yang.common.Decimal64; +import org.opendaylight.yangtools.yang.common.Uint16; +import org.opendaylight.yangtools.yang.common.Uint32; + +@ExtendWith(MockitoExtension.class) +class NetconfClientConfigurationBuilderFactoryImplTest { + private static final NodeId NODE_ID = new NodeId("testing-node"); + private static final Host HOST = new Host(new IpAddress(new Ipv4Address("127.0.0.1"))); + private static final PortNumber PORT = new PortNumber(Uint16.valueOf(9999)); + + @Mock + private NetconfClientSessionListener sessionListener; + @Mock + private AAAEncryptionService encryptionService; + @Mock + private CredentialProvider credentialProvider; + @Mock + private SslHandlerFactoryProvider sslHandlerFactoryProvider; + + private NetconfNodeBuilder nodeBuilder; + private NetconfClientConfigurationBuilderFactoryImpl factory; + + @BeforeEach + void beforeEach() { + nodeBuilder = new NetconfNodeBuilder() + .setHost(HOST).setPort(PORT) + .setReconnectOnChangedSchema(true) + .setDefaultRequestTimeoutMillis(Uint32.valueOf(1000)) + .setBetweenAttemptsTimeoutMillis(Uint16.valueOf(100)) + .setKeepaliveDelay(Uint32.valueOf(1000)) + .setCredentials(new LoginPwUnencryptedBuilder() + .setLoginPasswordUnencrypted(new LoginPasswordUnencryptedBuilder() + .setUsername("test-user") + .setPassword("test-password") + .build()) + .build()) + .setMaxConnectionAttempts(Uint32.ZERO) + .setSleepFactor(Decimal64.valueOf("1.5")) + .setConnectionTimeoutMillis(Uint32.valueOf(20000)); + factory = new NetconfClientConfigurationBuilderFactoryImpl(encryptionService, credentialProvider, + sslHandlerFactoryProvider); + } + + private void assertConfig(NetconfClientConfiguration config) { + assertNotNull(config); + assertNotNull(config.getTcpParameters()); + assertEquals(HOST, config.getTcpParameters().getRemoteAddress()); + assertEquals(PORT, config.getTcpParameters().getRemotePort()); + assertSame(sessionListener, config.getSessionListener()); + } + + @Test + void testDefault() { + final var config = createConfig(nodeBuilder.setTcpOnly(false).build()); + assertConfig(config); + assertEquals(NetconfClientProtocol.SSH, config.getProtocol()); + assertNotNull(config.getSshParameters()); + } + + @Test + void testSsh() { + final var config = createConfig( + nodeBuilder.setTcpOnly(false).setProtocol(new ProtocolBuilder().setName(Name.SSH).build()).build()); + assertConfig(config); + assertEquals(NetconfClientProtocol.SSH, config.getProtocol()); + assertNotNull(config.getSshParameters()); + } + + @Test + void testTcp() { + final var config = createConfig(nodeBuilder.setTcpOnly(true).build()); + assertConfig(config); + assertEquals(NetconfClientProtocol.TCP, config.getProtocol()); + } + + @Test + void testTls() { + final var config = createConfig( + nodeBuilder.setTcpOnly(false).setProtocol(new ProtocolBuilder().setName(Name.TLS).build()).build()); + assertConfig(config); + assertEquals(NetconfClientProtocol.TLS, config.getProtocol()); + assertNotNull(config.getTransportSslHandlerFactory()); + } + + @Test + void noPort() { + assertThrows(NoSuchElementException.class, () -> createConfig(nodeBuilder.setPort(null).build())); + } + + @Test + void noHost() { + assertThrows(NoSuchElementException.class, () -> createConfig(nodeBuilder.setHost(null).build())); + } + + private NetconfClientConfiguration createConfig(final NetconfNode netconfNode) { + return factory.createClientConfigurationBuilder(NODE_ID, netconfNode) + .withSessionListener(sessionListener) + .build(); + } +} diff --git a/apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandlerTest.java b/apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandlerTest.java index 3742b1c848..7a5bc3b190 100644 --- a/apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandlerTest.java +++ b/apps/netconf-topology/src/test/java/org/opendaylight/netconf/topology/spi/NetconfNodeHandlerTest.java @@ -11,6 +11,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -20,15 +21,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import com.google.common.net.InetAddresses; -import io.netty.util.concurrent.DefaultPromise; -import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.ImmediateEventExecutor; -import io.netty.util.concurrent.ScheduledFuture; -import io.netty.util.concurrent.SucceededFuture; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.SettableFuture; import java.net.InetSocketAddress; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.BeforeClass; @@ -40,7 +39,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.opendaylight.aaa.encrypt.AAAEncryptionService; import org.opendaylight.netconf.api.CapabilityURN; -import org.opendaylight.netconf.client.NetconfClientDispatcher; +import org.opendaylight.netconf.client.NetconfClientFactory; import org.opendaylight.netconf.client.NetconfClientSession; import org.opendaylight.netconf.client.mdsal.NetconfDeviceCapabilities; import org.opendaylight.netconf.client.mdsal.NetconfDeviceSchema; @@ -80,7 +79,7 @@ public class NetconfNodeHandlerTest { // Core setup @Mock - private ScheduledExecutorService keepaliveExecutor; + private ScheduledExecutorService scheduledExecutor; @Mock private SchemaResourceManager schemaManager; @Mock @@ -100,7 +99,7 @@ public class NetconfNodeHandlerTest { // Mock client dispatcher-related things @Mock - private NetconfClientDispatcher clientDispatcher; + private NetconfClientFactory clientFactory; @Mock private NetconfClientSession clientSession; @Captor @@ -112,8 +111,6 @@ public class NetconfNodeHandlerTest { // Mock eventExecutor-related things @Mock - private EventExecutor eventExecutor; - @Mock private ScheduledFuture scheduleFuture; @Captor private ArgumentCaptor scheduleCaptor; @@ -135,7 +132,7 @@ public class NetconfNodeHandlerTest { @Before public void before() { // Instantiate the handler - handler = new NetconfNodeHandler(clientDispatcher, eventExecutor, keepaliveExecutor, BASE_SCHEMAS, + handler = new NetconfNodeHandler(clientFactory, scheduledExecutor, BASE_SCHEMAS, schemaManager, processingExecutor, new DefaultNetconfClientConfigurationBuilderFactory(encryptionService, credentialProvider, sslHandlerFactoryProvider), @@ -163,7 +160,7 @@ public class NetconfNodeHandlerTest { } @Test - public void successfullOnDeviceConnectedPropagates() { + public void successfulOnDeviceConnectedPropagates() throws Exception { assertSuccessfulConnect(); assertEquals(1, handler.attempts()); @@ -184,31 +181,32 @@ public class NetconfNodeHandlerTest { } @Test - public void failedSchemaCausesReconnect() { + public void failedSchemaCausesReconnect() throws Exception { assertSuccessfulConnect(); assertEquals(1, handler.attempts()); // Note: this will count as a second attempt - doReturn(scheduleFuture).when(eventExecutor).schedule(scheduleCaptor.capture(), eq(150L), - eq(TimeUnit.MILLISECONDS)); + doReturn(scheduleFuture).when(scheduledExecutor) + .schedule(scheduleCaptor.capture(), anyLong(), any(TimeUnit.class)); + handler.onDeviceFailed(new AssertionError("schema failure")); assertEquals(2, handler.attempts()); // and when we run the task, we get a clientDispatcher invocation, but attempts are still the same scheduleCaptor.getValue().run(); - verify(clientDispatcher, times(2)).createClient(any()); + verify(clientFactory, times(2)).createClient(any()); assertEquals(2, handler.attempts()); } @Test - public void downAfterUpCausesReconnect() { + public void downAfterUpCausesReconnect() throws Exception { // Let's borrow common bits - successfullOnDeviceConnectedPropagates(); + successfulOnDeviceConnectedPropagates(); // when the device is connected, we propagate the information and initiate reconnect doNothing().when(delegate).onDeviceDisconnected(); - doReturn(scheduleFuture).when(eventExecutor).schedule(scheduleCaptor.capture(), eq(100L), + doReturn(scheduleFuture).when(scheduledExecutor).schedule(scheduleCaptor.capture(), eq(100L), eq(TimeUnit.MILLISECONDS)); handler.onDeviceDisconnected(); @@ -216,46 +214,45 @@ public class NetconfNodeHandlerTest { // and when we run the task, we get a clientDispatcher invocation, but attempts are still the same scheduleCaptor.getValue().run(); - verify(clientDispatcher, times(2)).createClient(any()); + verify(clientFactory, times(2)).createClient(any()); assertEquals(1, handler.attempts()); } @Test - public void socketFailuresAreRetried() { - final var firstPromise = new DefaultPromise(ImmediateEventExecutor.INSTANCE); - final var secondPromise = new DefaultPromise(ImmediateEventExecutor.INSTANCE); - doReturn(firstPromise, secondPromise).when(clientDispatcher).createClient(any()); + public void socketFailuresAreRetried() throws Exception { + final var firstFuture = SettableFuture.create(); + final var secondFuture = SettableFuture.create(); + doReturn(firstFuture, secondFuture).when(clientFactory).createClient(any()); handler.connect(); assertEquals(1, handler.attempts()); - doReturn(scheduleFuture).when(eventExecutor).schedule(scheduleCaptor.capture(), eq(150L), + doReturn(scheduleFuture).when(scheduledExecutor).schedule(scheduleCaptor.capture(), eq(150L), eq(TimeUnit.MILLISECONDS)); - firstPromise.setFailure(new AssertionError("first")); + firstFuture.setException(new AssertionError("first")); assertEquals(2, handler.attempts()); // and when we run the task, we get a clientDispatcher invocation, but attempts are still the same scheduleCaptor.getValue().run(); - verify(clientDispatcher, times(2)).createClient(any()); + verify(clientFactory, times(2)).createClient(any()); assertEquals(2, handler.attempts()); // now report the second failure final var throwableCaptor = ArgumentCaptor.forClass(Throwable.class); doNothing().when(delegate).onDeviceFailed(throwableCaptor.capture()); - secondPromise.setFailure(new AssertionError("second")); + secondFuture.setException(new AssertionError("second")); assertThat(throwableCaptor.getValue(), instanceOf(ConnectGivenUpException.class)); // but nothing else happens assertEquals(2, handler.attempts()); } - // Initiate a connect() which results in immediate clientDispatcher report. No interactions with delegate may occur, + // Initiate connect() which results in immediate clientDispatcher report. No interactions with delegate may occur, // as this is just a prelude to a follow-up callback - private void assertSuccessfulConnect() { - doReturn(new SucceededFuture<>(ImmediateEventExecutor.INSTANCE, clientSession)) - .when(clientDispatcher).createClient(any()); + private void assertSuccessfulConnect() throws Exception { + doReturn(Futures.immediateFuture(clientSession)).when(clientFactory).createClient(any()); handler.connect(); - verify(clientDispatcher).createClient(any()); + verify(clientFactory).createClient(any()); verifyNoInteractions(delegate); } } diff --git a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/CallHomeChannelActivator.java b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/CallHomeChannelActivator.java index 2ed801b55f..434ec5b3f2 100644 --- a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/CallHomeChannelActivator.java +++ b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/CallHomeChannelActivator.java @@ -7,7 +7,7 @@ */ package org.opendaylight.netconf.callhome.protocol; -import io.netty.util.concurrent.Future; +import com.google.common.util.concurrent.ListenableFuture; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.netconf.client.NetconfClientSession; import org.opendaylight.netconf.client.NetconfClientSessionListener; @@ -26,5 +26,5 @@ public interface CallHomeChannelActivator { * @param listener Client Session Listener to be attached to NETCONF session. * @return Future with negotiated NETCONF session */ - @NonNull Future activate(@NonNull NetconfClientSessionListener listener); + @NonNull ListenableFuture activate(@NonNull NetconfClientSessionListener listener); } diff --git a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/CallHomeSessionContext.java b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/CallHomeSessionContext.java index 8c3d10048e..b2e41e7eb0 100644 --- a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/CallHomeSessionContext.java +++ b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/CallHomeSessionContext.java @@ -11,6 +11,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; @@ -123,10 +126,10 @@ class CallHomeSessionContext implements CallHomeProtocolSessionContext { sshSession.close(false); } - private synchronized Promise doActivate(final ClientChannel netconfChannel, + private synchronized ListenableFuture doActivate(final ClientChannel netconfChannel, final NetconfClientSessionListener listener, final MinaSshNettyChannel nettyChannel) { if (activated) { - return newSessionPromise().setFailure(new IllegalStateException("Session already activated.")); + return Futures.immediateFailedFuture(new IllegalStateException("Session already activated.")); } activated = true; nettyChannel.pipeline().addFirst(new SshWriteAsyncHandlerAdapter(netconfChannel)); @@ -135,11 +138,21 @@ class CallHomeSessionContext implements CallHomeProtocolSessionContext { factory.getChannelInitializer(listener).initialize(nettyChannel, activationPromise); ((ChannelSubsystem) netconfChannel).onClose(nettyChannel::doNettyDisconnect); factory.getNettyGroup().register(nettyChannel).awaitUninterruptibly(500); - return activationPromise; + final SettableFuture future = SettableFuture.create(); + activationPromise.addListener(ignored -> { + final var cause = activationPromise.cause(); + if (cause != null) { + future.setException(cause); + } else { + future.set(activationPromise.getNow()); + } + }); + return future; } @Deprecated(since = "7.0.0", forRemoval = true) - protected MinaSshNettyChannel newMinaSshNettyChannel() { + @VisibleForTesting + MinaSshNettyChannel newMinaSshNettyChannel() { return new MinaSshNettyChannel(this, sshSession); } diff --git a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/CallHomeTlsSessionContext.java b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/CallHomeTlsSessionContext.java index f8f9e458ec..b8a5967edc 100644 --- a/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/CallHomeTlsSessionContext.java +++ b/netconf/callhome-protocol/src/main/java/org/opendaylight/netconf/callhome/protocol/tls/CallHomeTlsSessionContext.java @@ -9,6 +9,9 @@ package org.opendaylight.netconf.callhome.protocol.tls; import static java.util.Objects.requireNonNull; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; import io.netty.channel.Channel; import io.netty.handler.ssl.SslHandler; import io.netty.util.HashedWheelTimer; @@ -67,10 +70,11 @@ final class CallHomeTlsSessionContext implements CallHomeProtocolSessionContext channel.close(); } - private Promise doActivate(final Channel ch, final NetconfClientSessionListener listener) { + private ListenableFuture doActivate(final Channel ch, + final NetconfClientSessionListener listener) { final Promise activationPromise = newSessionPromise(); if (activated.compareAndExchange(false, true)) { - return activationPromise.setFailure(new IllegalStateException("Session (channel) already activated.")); + return Futures.immediateFailedFuture(new IllegalStateException("Session (channel) already activated.")); } LOG.info("Activating Netconf channel for {} with {}", getRemoteAddress(), listener); @@ -79,7 +83,16 @@ final class CallHomeTlsSessionContext implements CallHomeProtocolSessionContext final TlsClientChannelInitializer tlsClientChannelInitializer = new TlsClientChannelInitializer( sslHandlerFactory, negotiatorFactory, listener); tlsClientChannelInitializer.initialize(ch, activationPromise); - return activationPromise; + final SettableFuture future = SettableFuture.create(); + activationPromise.addListener(ignored -> { + final var cause = activationPromise.cause(); + if (cause != null) { + future.setException(cause); + } else { + future.set(activationPromise.getNow()); + } + }); + return future; } @Override -- 2.36.6