/* * Copyright (c) 2015 Cisco Systems, Inc. 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; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import io.netty.util.concurrent.EventExecutor; import java.io.File; import java.math.BigDecimal; import java.net.InetSocketAddress; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Map; import org.opendaylight.controller.config.threadpool.ScheduledThreadPool; import org.opendaylight.controller.config.threadpool.ThreadPool; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService; import org.opendaylight.controller.sal.binding.api.BindingAwareBroker; import org.opendaylight.controller.sal.core.api.Broker; import org.opendaylight.netconf.api.NetconfMessage; import org.opendaylight.netconf.client.NetconfClientDispatcher; import org.opendaylight.netconf.client.NetconfClientSessionListener; import org.opendaylight.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration; import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder; import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler; import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword; import org.opendaylight.netconf.sal.connect.api.RemoteDevice; import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler; import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas; import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice; import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder; import org.opendaylight.netconf.sal.connect.netconf.NetconfStateSchemasResolverImpl; import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator; import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences; import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences; import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade; import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider; import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId; import org.opendaylight.netconf.topology.api.NetconfTopology; import org.opendaylight.netconf.topology.api.SchemaRepositoryProvider; import org.opendaylight.protocol.framework.ReconnectStrategy; import org.opendaylight.protocol.framework.ReconnectStrategyFactory; import org.opendaylight.protocol.framework.TimedReconnectStrategy; 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.opendaylight.netconf.node.topology.rev150114.NetconfNode; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin; import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials; 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.yangtools.yang.model.repo.api.SchemaContextFactory; import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository; import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceFilter; import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource; import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry; import org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache; import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository; import org.opendaylight.yangtools.yang.parser.util.TextToASTTransformer; import org.slf4j.Logger; 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 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); // constants related to Schema Cache(s) /** * Filesystem based caches are stored relative to the cache directory. */ private static final String CACHE_DIRECTORY = "cache"; /** * The default cache directory relative to CACHE_DIRECTORY */ private static final String DEFAULT_CACHE_DIRECTORY = "schema"; /** * The qualified schema cache directory cache/schema */ private static final String QUALIFIED_DEFAULT_CACHE_DIRECTORY = CACHE_DIRECTORY + File.separator+ DEFAULT_CACHE_DIRECTORY; /** * The name for the default schema repository */ private static final String DEFAULT_SCHEMA_REPOSITORY_NAME = "sal-netconf-connector"; /** * The default schema repository in the case that one is not specified. */ private static final SharedSchemaRepository DEFAULT_SCHEMA_REPOSITORY = new SharedSchemaRepository(DEFAULT_SCHEMA_REPOSITORY_NAME); /** * The default FilesystemSchemaSourceCache, which stores cached files in cache/schema. */ private static final FilesystemSchemaSourceCache DEFAULT_CACHE = new FilesystemSchemaSourceCache<>(DEFAULT_SCHEMA_REPOSITORY, YangTextSchemaSource.class, new File(QUALIFIED_DEFAULT_CACHE_DIRECTORY)); /** * The default factory for creating SchemaContext instances. */ private static final SchemaContextFactory DEFAULT_SCHEMA_CONTEXT_FACTORY = DEFAULT_SCHEMA_REPOSITORY.createSchemaContextFactory(SchemaSourceFilter.ALWAYS_ACCEPT); /** * Keeps track of initialized Schema resources. A Map is maintained in which the key represents the name * of the schema cache directory, and the value is a corresponding SchemaResourcesDTO. The * SchemaResourcesDTO is essentially a container that allows for the extraction of the * SchemaRegistry and SchemaContextFactory which should be used for a particular * Netconf mount. Access to schemaResourcesDTOs should be surrounded by appropriate * synchronization locks. */ private static volatile Map schemaResourcesDTOs = new HashMap<>(); // Initializes default constant instances for the case when the default schema repository // directory cache/schema is used. static { schemaResourcesDTOs.put(DEFAULT_CACHE_DIRECTORY, new NetconfDevice.SchemaResourcesDTO(DEFAULT_SCHEMA_REPOSITORY, DEFAULT_SCHEMA_REPOSITORY, DEFAULT_SCHEMA_CONTEXT_FACTORY, new NetconfStateSchemasResolverImpl())); DEFAULT_SCHEMA_REPOSITORY.registerSchemaSourceListener(DEFAULT_CACHE); DEFAULT_SCHEMA_REPOSITORY.registerSchemaSourceListener( TextToASTTransformer.create(DEFAULT_SCHEMA_REPOSITORY, DEFAULT_SCHEMA_REPOSITORY)); } protected final String topologyId; private final NetconfClientDispatcher clientDispatcher; protected final BindingAwareBroker bindingAwareBroker; protected final Broker domBroker; private final EventExecutor eventExecutor; protected final ScheduledThreadPool keepaliveExecutor; protected final ThreadPool processingExecutor; protected final SharedSchemaRepository sharedSchemaRepository; protected final DataBroker dataBroker; protected final DOMMountPointService mountPointService; protected SchemaSourceRegistry schemaRegistry = DEFAULT_SCHEMA_REPOSITORY; protected SchemaRepository schemaRepository = DEFAULT_SCHEMA_REPOSITORY; protected SchemaContextFactory schemaContextFactory = DEFAULT_SCHEMA_CONTEXT_FACTORY; protected final HashMap activeConnectors = new HashMap<>(); protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher, final BindingAwareBroker bindingAwareBroker, final Broker domBroker, final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor, final ThreadPool processingExecutor, final SchemaRepositoryProvider schemaRepositoryProvider, final DataBroker dataBroker, final DOMMountPointService mountPointService) { this.topologyId = topologyId; this.clientDispatcher = clientDispatcher; this.bindingAwareBroker = bindingAwareBroker; this.domBroker = domBroker; this.eventExecutor = eventExecutor; this.keepaliveExecutor = keepaliveExecutor; this.processingExecutor = processingExecutor; this.sharedSchemaRepository = schemaRepositoryProvider.getSharedSchemaRepository(); this.dataBroker = dataBroker; this.mountPointService = mountPointService; } public void setSchemaRegistry(final SchemaSourceRegistry schemaRegistry) { this.schemaRegistry = schemaRegistry; } public void setSchemaContextFactory(final SchemaContextFactory schemaContextFactory) { this.schemaContextFactory = schemaContextFactory; } @Override public ListenableFuture connectNode(NodeId nodeId, Node configNode) { LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, configNode); return setupConnection(nodeId, configNode); } @Override public ListenableFuture disconnectNode(NodeId nodeId) { LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue()); if (!activeConnectors.containsKey(nodeId)) { return Futures.immediateFailedFuture(new IllegalStateException("Unable to disconnect device that is not connected")); } // retrieve connection, and disconnect it final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId); connectorDTO.getCommunicator().close(); connectorDTO.getFacade().close(); return Futures.immediateFuture(null); } protected ListenableFuture setupConnection(final NodeId nodeId, final Node configNode) { final NetconfNode netconfNode = configNode.getAugmentation(NetconfNode.class); Preconditions.checkNotNull(netconfNode.getHost()); Preconditions.checkNotNull(netconfNode.getPort()); Preconditions.checkNotNull(netconfNode.isTcpOnly()); final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode); final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator(); final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener(); final NetconfReconnectingClientConfiguration clientConfig = getClientConfig(netconfClientSessionListener, netconfNode); final ListenableFuture future = deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig); activeConnectors.put(nodeId, deviceCommunicatorDTO); Futures.addCallback(future, new FutureCallback() { @Override public void onSuccess(NetconfDeviceCapabilities result) { LOG.debug("Connector for : " + nodeId.getValue() + " started succesfully"); } @Override public void onFailure(Throwable t) { LOG.error("Connector for : " + nodeId.getValue() + " failed"); // remove this node from active connectors? } }); return future; } protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) { //setup default values since default value is not supported in mdsal final Long defaultRequestTimeoutMillis = node.getDefaultRequestTimeoutMillis() == null ? DEFAULT_REQUEST_TIMEOUT_MILLIS : node.getDefaultRequestTimeoutMillis(); final Long keepaliveDelay = node.getKeepaliveDelay() == null ? DEFAULT_KEEPALIVE_DELAY : node.getKeepaliveDelay(); final Boolean reconnectOnChangedSchema = node.isReconnectOnChangedSchema() == null ? DEFAULT_RECONNECT_ON_CHANGED_SCHEMA : node.isReconnectOnChangedSchema(); IpAddress ipAddress = node.getHost().getIpAddress(); InetSocketAddress address = new InetSocketAddress(ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue(), node.getPort().getValue()); RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address); RemoteDeviceHandler salFacade = createSalFacade(remoteDeviceId, domBroker, bindingAwareBroker); if (keepaliveDelay > 0) { LOG.warn("Adding keepalive facade, for device {}", nodeId); salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(), keepaliveDelay, defaultRequestTimeoutMillis); } // pre register yang library sources as fallback schemas to schema registry List> registeredYangLibSources = Lists.newArrayList(); if (node.getYangLibrary() != null) { final String yangLibURL = node.getYangLibrary().getYangLibraryUrl().getValue(); final String yangLibUsername = node.getYangLibrary().getUsername(); final String yangLigPassword = node.getYangLibrary().getPassword(); LibraryModulesSchemas libraryModulesSchemas; if(yangLibURL != null) { if(yangLibUsername != null && yangLigPassword != null) { libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword); } else { libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL); } for (Map.Entry sourceIdentifierURLEntry : libraryModulesSchemas.getAvailableModels().entrySet()) { registeredYangLibSources. add(schemaRegistry.registerSchemaSource( new YangLibrarySchemaYangSourceProvider(remoteDeviceId, libraryModulesSchemas.getAvailableModels()), PotentialSchemaSource .create(sourceIdentifierURLEntry.getKey(), YangTextSchemaSource.class, PotentialSchemaSource.Costs.REMOTE_IO.getValue()))); } } } final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node); final RemoteDevice device; if (node.isSchemaless()) { device = new SchemalessNetconfDevice(remoteDeviceId, salFacade); } else { device = new NetconfDeviceBuilder() .setReconnectOnSchemasChange(reconnectOnChangedSchema) .setSchemaResourcesDTO(schemaResourcesDTO) .setGlobalProcessingExecutor(processingExecutor.getExecutor()) .setId(remoteDeviceId) .setSalFacade(salFacade) .build(); } final Optional userCapabilities = getUserCapabilities(node); final int rpcMessageLimit = node.getConcurrentRpcLimit() == null ? DEFAULT_CONCURRENT_RPC_LIMIT : node.getConcurrentRpcLimit(); if (rpcMessageLimit < 1) { LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId); } return new NetconfConnectorDTO( userCapabilities.isPresent() ? new NetconfDeviceCommunicator( remoteDeviceId, device, new UserPreferences(userCapabilities.get(), node.getYangModuleCapabilities().isOverride()), rpcMessageLimit): new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit), salFacade); } protected NetconfDevice.SchemaResourcesDTO setupSchemaCacheDTO(final NodeId nodeId, final NetconfNode node) { // Setup information related to the SchemaRegistry, SchemaResourceFactory, etc. NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = null; final String moduleSchemaCacheDirectory = node.getSchemaCacheDirectory(); // Only checks to ensure the String is not empty or null; further checks related to directory accessibility and file permissions // are handled during the FilesystemSchemaSourceCache initialization. if (!Strings.isNullOrEmpty(moduleSchemaCacheDirectory)) { // If a custom schema cache directory is specified, create the backing DTO; otherwise, the SchemaRegistry and // SchemaContextFactory remain the default values. if (!moduleSchemaCacheDirectory.equals(DEFAULT_CACHE_DIRECTORY)) { // Multiple modules may be created at once; synchronize to avoid issues with data consistency among threads. synchronized(schemaResourcesDTOs) { // Look for the cached DTO to reuse SchemaRegistry and SchemaContextFactory variables if they already exist schemaResourcesDTO = schemaResourcesDTOs.get(moduleSchemaCacheDirectory); if (schemaResourcesDTO == null) { schemaResourcesDTO = createSchemaResourcesDTO(moduleSchemaCacheDirectory); schemaResourcesDTO.getSchemaRegistry().registerSchemaSourceListener( TextToASTTransformer.create((SchemaRepository) schemaResourcesDTO.getSchemaRegistry(), schemaResourcesDTO.getSchemaRegistry()) ); schemaResourcesDTOs.put(moduleSchemaCacheDirectory, schemaResourcesDTO); } } LOG.info("Netconf connector for device {} will use schema cache directory {} instead of {}", nodeId.getValue(), moduleSchemaCacheDirectory, DEFAULT_CACHE_DIRECTORY); } } else { LOG.warn("schema-cache-directory for {} is null or empty; using the default {}", nodeId.getValue(), QUALIFIED_DEFAULT_CACHE_DIRECTORY); } if (schemaResourcesDTO == null) { schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry, schemaRepository, schemaContextFactory, new NetconfStateSchemasResolverImpl()); } return schemaResourcesDTO; } /** * Creates the backing Schema classes for a particular directory. * * @param moduleSchemaCacheDirectory The string directory relative to "cache" * @return A DTO containing the Schema classes for the Netconf mount. */ private NetconfDevice.SchemaResourcesDTO createSchemaResourcesDTO(final String moduleSchemaCacheDirectory) { final SharedSchemaRepository repository = new SharedSchemaRepository(moduleSchemaCacheDirectory); final SchemaContextFactory schemaContextFactory = repository.createSchemaContextFactory(SchemaSourceFilter.ALWAYS_ACCEPT); setSchemaRegistry(repository); setSchemaContextFactory(schemaContextFactory); final FilesystemSchemaSourceCache deviceCache = createDeviceFilesystemCache(moduleSchemaCacheDirectory); repository.registerSchemaSourceListener(deviceCache); return new NetconfDevice.SchemaResourcesDTO(repository, repository, schemaContextFactory, new NetconfStateSchemasResolverImpl()); } /** * Creates a FilesystemSchemaSourceCache for the custom schema cache directory. * * @param schemaCacheDirectory The custom cache directory relative to "cache" * @return A FilesystemSchemaSourceCache for the custom schema cache directory */ private FilesystemSchemaSourceCache createDeviceFilesystemCache(final String schemaCacheDirectory) { final String relativeSchemaCacheDirectory = CACHE_DIRECTORY + File.separator + schemaCacheDirectory; return new FilesystemSchemaSourceCache<>(schemaRegistry, YangTextSchemaSource.class, new File(relativeSchemaCacheDirectory)); } public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener, 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(); final long maxConnectionAttempts = node.getMaxConnectionAttempts() == null ? DEFAULT_MAX_CONNECTION_ATTEMPTS : node.getMaxConnectionAttempts(); final int betweenAttemptsTimeoutMillis = node.getBetweenAttemptsTimeoutMillis() == null ? DEFAULT_BETWEEN_ATTEMPTS_TIMEOUT_MILLIS : node.getBetweenAttemptsTimeoutMillis(); final BigDecimal sleepFactor = node.getSleepFactor() == null ? DEFAULT_SLEEP_FACTOR : node.getSleepFactor(); final InetSocketAddress socketAddress = getSocketAddress(node.getHost(), node.getPort().getValue()); final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor, maxConnectionAttempts, betweenAttemptsTimeoutMillis, sleepFactor); final ReconnectStrategy strategy = sf.createReconnectStrategy(); final AuthenticationHandler authHandler; final Credentials credentials = node.getCredentials(); if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPassword) { authHandler = new LoginPassword( ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPassword) credentials).getUsername(), ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPassword) credentials).getPassword()); } else { throw new IllegalStateException("Only login/password authentification is supported"); } return NetconfReconnectingClientConfigurationBuilder.create() .withAddress(socketAddress) .withConnectionTimeoutMillis(clientConnectionTimeoutMillis) .withReconnectStrategy(strategy) .withAuthHandler(authHandler) .withProtocol(node.isTcpOnly() ? NetconfClientConfiguration.NetconfClientProtocol.TCP : NetconfClientConfiguration.NetconfClientProtocol.SSH) .withConnectStrategyFactory(sf) .withSessionListener(listener) .build(); } protected abstract RemoteDeviceHandler createSalFacade(final RemoteDeviceId id, final Broker domBroker, final BindingAwareBroker bindingBroker); private InetSocketAddress getSocketAddress(final Host host, int port) { if(host.getDomainName() != null) { return new InetSocketAddress(host.getDomainName().getValue(), port); } else { final IpAddress ipAddress = host.getIpAddress(); final String ip = ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue(); return new InetSocketAddress(ip, port); } } private Optional getUserCapabilities(final NetconfNode node) { if(node.getYangModuleCapabilities() == null) { return Optional.absent(); } final List capabilities = node.getYangModuleCapabilities().getCapability(); if(capabilities == null || capabilities.isEmpty()) { return Optional.absent(); } final NetconfSessionPreferences parsedOverrideCapabilities = NetconfSessionPreferences.fromStrings(capabilities, CapabilityOrigin.UserDefined); Preconditions.checkState(parsedOverrideCapabilities.getNonModuleCaps().isEmpty(), "Capabilities to override can " + "only contain module based capabilities, non-module capabilities will be retrieved from the device," + " configured non-module capabilities: " + parsedOverrideCapabilities.getNonModuleCaps()); return Optional.of(parsedOverrideCapabilities); } private static final class TimedReconnectStrategyFactory implements ReconnectStrategyFactory { private final Long connectionAttempts; private final EventExecutor executor; private final double sleepFactor; private final int minSleep; TimedReconnectStrategyFactory(final EventExecutor executor, final Long maxConnectionAttempts, final int minSleep, final BigDecimal sleepFactor) { if (maxConnectionAttempts != null && maxConnectionAttempts > 0) { connectionAttempts = maxConnectionAttempts; } else { connectionAttempts = null; } this.sleepFactor = sleepFactor.doubleValue(); this.executor = executor; this.minSleep = minSleep; } @Override public ReconnectStrategy createReconnectStrategy() { final Long maxSleep = null; final Long deadline = null; return new TimedReconnectStrategy(executor, minSleep, minSleep, sleepFactor, maxSleep, connectionAttempts, deadline); } } protected static class NetconfConnectorDTO implements AutoCloseable { private final NetconfDeviceCommunicator communicator; private final RemoteDeviceHandler facade; public NetconfConnectorDTO(final NetconfDeviceCommunicator communicator, final RemoteDeviceHandler facade) { this.communicator = communicator; this.facade = facade; } public NetconfDeviceCommunicator getCommunicator() { return communicator; } public RemoteDeviceHandler getFacade() { return facade; } public NetconfClientSessionListener getSessionListener() { return communicator; } @Override public void close() { communicator.close(); facade.close(); } } }