From 23a0035c3e7c633990619b10f133c31b0f1fc3d1 Mon Sep 17 00:00:00 2001 From: Ivan Hrasko Date: Mon, 6 Dec 2021 11:58:02 +0100 Subject: [PATCH] Refactor NETCONF node defaults In fact, the difference between devices connected by call-home and created manually in NETCONF topology is that call-home devices do not have any data in configuration data-store. Configuration data is used during the connection setup. We can create full default configuration node for call-home connected device to unify the processing of both kind of devices. In addition, MDSAL now supports default values in data-store so we can remove those values from AbstractNetconfTopology. JIRA: NETCONF-832 Change-Id: Ib58ca344a7d74612cb7d049805a3e3c9279986d4 Signed-off-by: Ivan Hrasko Signed-off-by: Robert Varga (cherry picked from commit 2dfc7efb77c6704a0cd397dbd82c2709b14d29ee) --- .../mount/CallHomeMountSessionContext.java | 44 ++++++++++--- .../CallHomeMountSessionContextTest.java | 3 +- .../impl/NetconfTopologyImplTest.java | 66 ++++--------------- .../topology/spi/AbstractNetconfTopology.java | 63 ++++-------------- 4 files changed, 65 insertions(+), 111 deletions(-) diff --git a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContext.java b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContext.java index 16257a76d2..5d30148108 100644 --- a/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContext.java +++ b/netconf/callhome-provider/src/main/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContext.java @@ -12,6 +12,7 @@ import static java.util.Objects.requireNonNull; import com.google.common.base.MoreObjects; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.netty.util.concurrent.Future; +import java.math.BigDecimal; import org.opendaylight.netconf.api.NetconfMessage; import org.opendaylight.netconf.api.NetconfTerminationReason; import org.opendaylight.netconf.callhome.protocol.CallHomeChannelActivator; @@ -20,10 +21,14 @@ import org.opendaylight.netconf.client.NetconfClientSession; import org.opendaylight.netconf.client.NetconfClientSessionListener; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.ProtocolBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordBuilder; 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; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder; +import org.opendaylight.yangtools.yang.common.Uint16; +import org.opendaylight.yangtools.yang.common.Uint32; // Non-final to allow mocking class CallHomeMountSessionContext { @@ -61,18 +66,41 @@ class CallHomeMountSessionContext { return key; } + /** + * Create device default configuration. + * + *

+ * This configuration is a replacement of configuration device data + * which is normally stored in configuration datastore but is absent for call-home devices. + * + * @return {@link Node} containing the default device configuration + */ + // FIXME make these defaults tune-able in odl-netconf-callhome-server Node getConfigNode() { return new NodeBuilder() .setNodeId(getId()) .addAugmentation(new NetconfNodeBuilder() - .setHost(new Host(key.getIpAddress())) - .setPort(key.getPort()) - .setTcpOnly(Boolean.FALSE) - .setCredentials(new LoginPasswordBuilder() - .setUsername("ommited") - .setPassword("ommited") - .build()) - .setSchemaless(Boolean.FALSE) + .setHost(new Host(key.getIpAddress())) + .setPort(key.getPort()) + .setTcpOnly(false) + .setProtocol(new ProtocolBuilder() + .setName(Protocol.Name.valueOf(protocol.getTransportType().name())) + .build()) + .setSchemaless(false) + .setReconnectOnChangedSchema(false) + .setConnectionTimeoutMillis(Uint32.valueOf(20000)) + .setDefaultRequestTimeoutMillis(Uint32.valueOf(60000)) + .setMaxConnectionAttempts(Uint32.ZERO) + .setBetweenAttemptsTimeoutMillis(Uint16.valueOf(2000)) + .setSleepFactor(new BigDecimal("1.5")) + .setKeepaliveDelay(Uint32.valueOf(120)) + .setConcurrentRpcLimit(Uint16.ZERO) + .setActorResponseWaitTime(Uint16.valueOf(5)) + // the real call-home device credentials are applied in CallHomeAuthProviderImpl + .setCredentials(new LoginPasswordBuilder() + .setUsername("omitted") + .setPassword("omitted") + .build()) .build()) .build(); } diff --git a/netconf/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContextTest.java b/netconf/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContextTest.java index 784168c81f..8e196568d8 100644 --- a/netconf/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContextTest.java +++ b/netconf/callhome-provider/src/test/java/org/opendaylight/netconf/callhome/mount/CallHomeMountSessionContextTest.java @@ -5,7 +5,6 @@ * 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.callhome.mount; import static org.junit.Assert.assertNotNull; @@ -25,6 +24,7 @@ import org.opendaylight.netconf.api.NetconfMessage; import org.opendaylight.netconf.api.NetconfTerminationReason; import org.opendaylight.netconf.callhome.protocol.CallHomeChannelActivator; import org.opendaylight.netconf.callhome.protocol.CallHomeProtocolSessionContext; +import org.opendaylight.netconf.callhome.protocol.TransportType; import org.opendaylight.netconf.client.NetconfClientSession; import org.opendaylight.netconf.client.NetconfClientSessionListener; @@ -45,6 +45,7 @@ public class CallHomeMountSessionContextTest { mockActivator = mock(CallHomeChannelActivator.class); mockCallback = mock(CallHomeMountSessionContext.CloseCallback.class); doReturn(someSocketAddress).when(mockProtocol).getRemoteAddress(); + doReturn(TransportType.SSH).when(mockProtocol).getTransportType(); instance = new CallHomeMountSessionContext("test",mockProtocol, mockActivator, mockCallback); } diff --git a/netconf/netconf-topology-impl/src/test/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImplTest.java b/netconf/netconf-topology-impl/src/test/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImplTest.java index 7607189c3b..6245fbaf0a 100644 --- a/netconf/netconf-topology-impl/src/test/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImplTest.java +++ b/netconf/netconf-topology-impl/src/test/java/org/opendaylight/netconf/topology/impl/NetconfTopologyImplTest.java @@ -26,6 +26,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import io.netty.util.concurrent.EventExecutor; +import java.math.BigDecimal; import java.util.Collection; import java.util.HashSet; import org.junit.Before; @@ -56,7 +57,6 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types. 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.node.topology.rev150114.NetconfNode; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.ProtocolBuilder; @@ -151,7 +151,6 @@ public class NetconfTopologyImplTest { @Test public void testOnDataTreeChange() { - final DataObjectModification newNode = mock(DataObjectModification.class); when(newNode.getModificationType()).thenReturn(DataObjectModification.ModificationType.WRITE); @@ -177,7 +176,6 @@ public class NetconfTopologyImplTest { when(newNode.getDataAfter()).thenReturn(nn.build()); - final Collection> changes = new HashSet<>(); final DataTreeModification ch = mock(DataTreeModification.class); when(ch.getRootNode()).thenReturn(newNode); @@ -195,83 +193,45 @@ public class NetconfTopologyImplTest { //one in previous creating and deleting node and one in updating verify(spyTopology, times(2)).disconnectNode(NetconfTopologyImpl.getNodeId(pa)); verify(spyTopology, times(2)).connectNode(NetconfTopologyImpl.getNodeId(pa), nn.build()); - - } @Test public void testGetClientConfig() { final NetconfClientSessionListener sessionListener = mock(NetconfClientSessionListener.class); - - final NetconfNode testingNode = new NetconfNodeBuilder() + final NetconfNodeBuilder nodeBuilder = new NetconfNodeBuilder() .setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1")))) .setPort(new PortNumber(Uint16.valueOf(9999))) .setReconnectOnChangedSchema(true) .setDefaultRequestTimeoutMillis(Uint32.valueOf(1000)) .setBetweenAttemptsTimeoutMillis(Uint16.valueOf(100)) .setKeepaliveDelay(Uint32.valueOf(1000)) - .setTcpOnly(true) - .setCredentials(new LoginPasswordBuilder() - .setUsername("testuser").setPassword("testpassword").build()) - .build(); + .setCredentials(new LoginPasswordBuilder().setUsername("testuser").setPassword("testpassword").build()) + .setMaxConnectionAttempts(Uint32.ZERO) + .setSleepFactor(new BigDecimal("1.5")) + .setConnectionTimeoutMillis(Uint32.valueOf(20000)); + final NetconfReconnectingClientConfiguration configuration = - spyTopology.getClientConfig(sessionListener, testingNode); + spyTopology.getClientConfig(sessionListener, nodeBuilder.setTcpOnly(true).build()); assertEquals(NetconfClientConfiguration.NetconfClientProtocol.TCP, configuration.getProtocol()); assertNotNull(configuration.getAuthHandler()); assertNull(configuration.getSslHandlerFactory()); - - final NetconfNode testingNode2 = new NetconfNodeBuilder() - .setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1")))) - .setPort(new PortNumber(Uint16.valueOf(9999))) - .setReconnectOnChangedSchema(true) - .setDefaultRequestTimeoutMillis(Uint32.valueOf(1000)) - .setBetweenAttemptsTimeoutMillis(Uint16.valueOf(100)) - .setKeepaliveDelay(Uint32.valueOf(1000)) - .setTcpOnly(false) - .setCredentials(new LoginPasswordBuilder() - .setUsername("testuser").setPassword("testpassword").build()) - .build(); final NetconfReconnectingClientConfiguration configuration2 = - spyTopology.getClientConfig(sessionListener, testingNode2); + spyTopology.getClientConfig(sessionListener, nodeBuilder.setTcpOnly(false).build()); assertEquals(NetconfClientConfiguration.NetconfClientProtocol.SSH, configuration2.getProtocol()); assertNotNull(configuration2.getAuthHandler()); assertNull(configuration2.getSslHandlerFactory()); - - final NetconfNode testingNode3 = new NetconfNodeBuilder() - .setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1")))) - .setPort(new PortNumber(Uint16.valueOf(9999))) - .setReconnectOnChangedSchema(true) - .setDefaultRequestTimeoutMillis(Uint32.valueOf(1000)) - .setBetweenAttemptsTimeoutMillis(Uint16.valueOf(100)) - .setKeepaliveDelay(Uint32.valueOf(1000)) - .setTcpOnly(false) - .setProtocol(new ProtocolBuilder().setName(Name.SSH).build()) - .setCredentials(new LoginPasswordBuilder() - .setUsername("testuser").setPassword("testpassword").build()) - .build(); final NetconfReconnectingClientConfiguration configuration3 = - spyTopology.getClientConfig(sessionListener, testingNode3); + spyTopology.getClientConfig(sessionListener, nodeBuilder + .setProtocol(new ProtocolBuilder().setName(Name.SSH).build()).build()); assertEquals(NetconfClientConfiguration.NetconfClientProtocol.SSH, configuration3.getProtocol()); assertNotNull(configuration3.getAuthHandler()); assertNull(configuration3.getSslHandlerFactory()); - - final NetconfNode testingNode4 = new NetconfNodeBuilder() - .setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1")))) - .setPort(new PortNumber(Uint16.valueOf(9999))) - .setReconnectOnChangedSchema(true) - .setDefaultRequestTimeoutMillis(Uint32.valueOf(1000)) - .setBetweenAttemptsTimeoutMillis(Uint16.valueOf(100)) - .setKeepaliveDelay(Uint32.valueOf(1000)) - .setTcpOnly(false) - .setProtocol(new ProtocolBuilder().setName(Name.TLS).build()) - .setCredentials(new LoginPasswordBuilder() - .setUsername("testuser").setPassword("testpassword").build()) - .build(); final NetconfReconnectingClientConfiguration configuration4 = - spyTopology.getClientConfig(sessionListener, testingNode4); + spyTopology.getClientConfig(sessionListener, nodeBuilder + .setProtocol(new ProtocolBuilder().setName(Name.TLS).build()).build()); assertEquals(NetconfClientConfiguration.NetconfClientProtocol.TLS, configuration4.getProtocol()); assertNull(configuration4.getAuthHandler()); assertNotNull(configuration4.getSslHandlerFactory()); diff --git a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/AbstractNetconfTopology.java b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/AbstractNetconfTopology.java index 8feec9d0e5..f134fe88e9 100644 --- a/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/AbstractNetconfTopology.java +++ b/netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/spi/AbstractNetconfTopology.java @@ -17,7 +17,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import io.netty.util.concurrent.EventExecutor; -import java.math.BigDecimal; import java.net.InetSocketAddress; import java.net.URL; import java.util.ArrayList; @@ -90,16 +89,6 @@ import org.slf4j.LoggerFactory; public abstract class AbstractNetconfTopology implements NetconfTopology { private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class); - protected static final long DEFAULT_REQUEST_TIMEOUT_MILLIS = 60000L; - protected static final int DEFAULT_KEEPALIVE_DELAY = 0; - protected static final boolean DEFAULT_RECONNECT_ON_CHANGED_SCHEMA = false; - protected static final int DEFAULT_CONCURRENT_RPC_LIMIT = 0; - private static final boolean DEFAULT_IS_TCP_ONLY = false; - private static final int DEFAULT_MAX_CONNECTION_ATTEMPTS = 0; - private static final int DEFAULT_BETWEEN_ATTEMPTS_TIMEOUT_MILLIS = 2000; - private static final long DEFAULT_CONNECTION_TIMEOUT_MILLIS = 20000L; - private static final BigDecimal DEFAULT_SLEEP_FACTOR = new BigDecimal(1.5); - private final NetconfClientDispatcher clientDispatcher; private final EventExecutor eventExecutor; private final DeviceActionFactory deviceActionFactory; @@ -117,7 +106,6 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { protected final AAAEncryptionService encryptionService; protected final HashMap activeConnectors = new HashMap<>(); - protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher, final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor, final SchemaResourceManager schemaManager, @@ -214,15 +202,9 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node, final NetconfNodeAugmentedOptional nodeOptional) { - //setup default values since default value is not supported in mdsal - final long defaultRequestTimeoutMillis = node.getDefaultRequestTimeoutMillis() == null - ? DEFAULT_REQUEST_TIMEOUT_MILLIS : node.getDefaultRequestTimeoutMillis().toJava(); - final long keepaliveDelay = node.getKeepaliveDelay() == null - ? DEFAULT_KEEPALIVE_DELAY : node.getKeepaliveDelay().toJava(); - - final InetSocketAddress address; final Host host = node.getHost(); final IpAddress ipAddress = host.getIpAddress(); + final InetSocketAddress address; if (ipAddress != null) { address = new InetSocketAddress(IetfInetUtil.INSTANCE.inetAddressFor(ipAddress), node.getPort().getValue().toJava()); @@ -232,12 +214,12 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { } final RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address); + final long keepaliveDelay = node.getKeepaliveDelay().toJava(); RemoteDeviceHandler salFacade = createSalFacade(remoteDeviceId); - if (keepaliveDelay > 0) { - LOG.warn("Adding keepalive facade, for device {}", nodeId); + LOG.info("Adding keepalive facade, for device {}", nodeId); salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, this.keepaliveExecutor.getExecutor(), - keepaliveDelay, defaultRequestTimeoutMillis); + keepaliveDelay, node.getDefaultRequestTimeoutMillis().toJava()); } final RemoteDevice device; @@ -246,9 +228,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { device = new SchemalessNetconfDevice(baseSchemas, remoteDeviceId, salFacade); yanglibRegistrations = List.of(); } else { - final boolean reconnectOnChangedSchema = node.getReconnectOnChangedSchema() == null - ? DEFAULT_RECONNECT_ON_CHANGED_SCHEMA : node.getReconnectOnChangedSchema(); - + final boolean reconnectOnChangedSchema = node.getReconnectOnChangedSchema(); final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node, nodeId.getValue()); device = new NetconfDeviceBuilder() .setReconnectOnSchemasChange(reconnectOnChangedSchema) @@ -262,18 +242,16 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { .setDeviceActionFactory(deviceActionFactory) .setBaseSchemas(baseSchemas) .build(); - yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, nodeId, node, resources); + yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources); } final Optional userCapabilities = getUserCapabilities(node); - final int rpcMessageLimit = node.getConcurrentRpcLimit() == null ? DEFAULT_CONCURRENT_RPC_LIMIT - : node.getConcurrentRpcLimit().toJava(); - + final int rpcMessageLimit = node.getConcurrentRpcLimit().toJava(); if (rpcMessageLimit < 1) { LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId); } - NetconfDeviceCommunicator netconfDeviceCommunicator = + final NetconfDeviceCommunicator netconfDeviceCommunicator = userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device, userCapabilities.get(), rpcMessageLimit) : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit); @@ -286,7 +264,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { } private List> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId, - final NodeId nodeId, final NetconfNode node, final SchemaResourcesDTO resources) { + final NetconfNode node, final SchemaResourcesDTO resources) { final YangLibrary yangLibrary = node.getYangLibrary(); if (yangLibrary != null) { final Uri uri = yangLibrary.getYangLibraryUrl(); @@ -334,25 +312,12 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener, final NetconfNode node) { - - //setup default values since default value is not supported in mdsal - final long clientConnectionTimeoutMillis = node.getConnectionTimeoutMillis() == null - ? DEFAULT_CONNECTION_TIMEOUT_MILLIS : node.getConnectionTimeoutMillis().toJava(); - final long maxConnectionAttempts = node.getMaxConnectionAttempts() == null - ? DEFAULT_MAX_CONNECTION_ATTEMPTS : node.getMaxConnectionAttempts().toJava(); - final int betweenAttemptsTimeoutMillis = node.getBetweenAttemptsTimeoutMillis() == null - ? DEFAULT_BETWEEN_ATTEMPTS_TIMEOUT_MILLIS : node.getBetweenAttemptsTimeoutMillis().toJava(); - final boolean useTcp = node.getTcpOnly() == null ? DEFAULT_IS_TCP_ONLY : node.getTcpOnly(); - final BigDecimal sleepFactor = node.getSleepFactor() == null ? DEFAULT_SLEEP_FACTOR : node.getSleepFactor(); - - final InetSocketAddress socketAddress = getSocketAddress(node.getHost(), node.getPort().getValue().toJava()); - final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor, - maxConnectionAttempts, betweenAttemptsTimeoutMillis, sleepFactor); - + node.getMaxConnectionAttempts().toJava(), node.getBetweenAttemptsTimeoutMillis().toJava(), + node.getSleepFactor()); final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder; final Protocol protocol = node.getProtocol(); - if (useTcp) { + if (node.getTcpOnly()) { reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create() .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP) .withAuthHandler(getHandlerFromCredentials(node.getCredentials())); @@ -374,8 +339,8 @@ public abstract class AbstractNetconfTopology implements NetconfTopology { } return reconnectingClientConfigurationBuilder - .withAddress(socketAddress) - .withConnectionTimeoutMillis(clientConnectionTimeoutMillis) + .withAddress(getSocketAddress(node.getHost(), node.getPort().getValue().toJava())) + .withConnectionTimeoutMillis(node.getConnectionTimeoutMillis().toJava()) .withReconnectStrategy(sf.createReconnectStrategy()) .withConnectStrategyFactory(sf) .withSessionListener(listener) -- 2.36.6