c7f5cf450988efd12356880aff73404aa1edc550
[netconf.git] / netconf / netconf-topology / src / main / java / org / opendaylight / netconf / topology / AbstractNetconfTopology.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.netconf.topology;
10
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Strings;
14 import com.google.common.collect.Lists;
15 import com.google.common.util.concurrent.FutureCallback;
16 import com.google.common.util.concurrent.Futures;
17 import com.google.common.util.concurrent.ListenableFuture;
18 import io.netty.util.concurrent.EventExecutor;
19 import java.io.File;
20 import java.math.BigDecimal;
21 import java.net.InetSocketAddress;
22 import java.net.URL;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
28 import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
29 import org.opendaylight.controller.config.threadpool.ThreadPool;
30 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
31 import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
32 import org.opendaylight.netconf.api.NetconfMessage;
33 import org.opendaylight.netconf.client.NetconfClientDispatcher;
34 import org.opendaylight.netconf.client.NetconfClientSessionListener;
35 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
36 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
37 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
38 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
39 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
40 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
41 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
42 import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
43 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
44 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
45 import org.opendaylight.netconf.sal.connect.netconf.NetconfStateSchemasResolverImpl;
46 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
47 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
48 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
49 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
50 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
51 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
52 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
53 import org.opendaylight.netconf.sal.connect.util.AuthEncryptor;
54 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
55 import org.opendaylight.netconf.topology.api.NetconfTopology;
56 import org.opendaylight.netconf.topology.api.SchemaRepositoryProvider;
57 import org.opendaylight.protocol.framework.ReconnectStrategy;
58 import org.opendaylight.protocol.framework.ReconnectStrategyFactory;
59 import org.opendaylight.protocol.framework.TimedReconnectStrategy;
60 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
65 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
66 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
67 import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
68 import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository;
69 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceFilter;
70 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
71 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
72 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
73 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
74 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
75 import org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache;
76 import org.opendaylight.yangtools.yang.parser.repo.SharedSchemaRepository;
77 import org.opendaylight.yangtools.yang.parser.util.TextToASTTransformer;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80
81 public abstract class AbstractNetconfTopology implements NetconfTopology {
82
83     private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
84
85     protected static final long DEFAULT_REQUEST_TIMEOUT_MILLIS = 60000L;
86     protected static final int DEFAULT_KEEPALIVE_DELAY = 0;
87     protected static final boolean DEFAULT_RECONNECT_ON_CHANGED_SCHEMA = false;
88     protected static final int DEFAULT_CONCURRENT_RPC_LIMIT = 0;
89     private static final int DEFAULT_MAX_CONNECTION_ATTEMPTS = 0;
90     private static final int DEFAULT_BETWEEN_ATTEMPTS_TIMEOUT_MILLIS = 2000;
91     private static final long DEFAULT_CONNECTION_TIMEOUT_MILLIS = 20000L;
92     private static final BigDecimal DEFAULT_SLEEP_FACTOR = new BigDecimal(1.5);
93
94     // constants related to Schema Cache(s)
95     /**
96      * Filesystem based caches are stored relative to the cache directory.
97      */
98     private static final String CACHE_DIRECTORY = "cache";
99
100     /**
101      * The default cache directory relative to <code>CACHE_DIRECTORY</code>.
102      */
103     private static final String DEFAULT_CACHE_DIRECTORY = "schema";
104
105     /**
106      * The qualified schema cache directory <code>cache/schema</code>.
107      */
108     private static final String QUALIFIED_DEFAULT_CACHE_DIRECTORY =
109             CACHE_DIRECTORY + File.separator + DEFAULT_CACHE_DIRECTORY;
110
111     /**
112      * The name for the default schema repository.
113      */
114     private static final String DEFAULT_SCHEMA_REPOSITORY_NAME = "sal-netconf-connector";
115
116     /**
117      * The default schema repository in the case that one is not specified.
118      */
119     private static final SharedSchemaRepository DEFAULT_SCHEMA_REPOSITORY =
120             new SharedSchemaRepository(DEFAULT_SCHEMA_REPOSITORY_NAME);
121
122     /**
123      * The default <code>FilesystemSchemaSourceCache</code>, which stores cached files in <code>cache/schema</code>.
124      */
125     private static final FilesystemSchemaSourceCache<YangTextSchemaSource> DEFAULT_CACHE =
126             new FilesystemSchemaSourceCache<>(DEFAULT_SCHEMA_REPOSITORY, YangTextSchemaSource.class,
127                     new File(QUALIFIED_DEFAULT_CACHE_DIRECTORY));
128
129     /**
130      * The default factory for creating <code>SchemaContext</code> instances.
131      */
132     private static final SchemaContextFactory DEFAULT_SCHEMA_CONTEXT_FACTORY =
133             DEFAULT_SCHEMA_REPOSITORY.createSchemaContextFactory(SchemaSourceFilter.ALWAYS_ACCEPT);
134
135     /**
136      * Keeps track of initialized Schema resources.  A Map is maintained in which the key represents the name
137      * of the schema cache directory, and the value is a corresponding <code>SchemaResourcesDTO</code>.  The
138      * <code>SchemaResourcesDTO</code> is essentially a container that allows for the extraction of the
139      * <code>SchemaRegistry</code> and <code>SchemaContextFactory</code> which should be used for a particular
140      * Netconf mount.  Access to <code>SCHEMA_RESOURCES_DTO_MAP</code> should be surrounded by appropriate
141      * synchronization locks.
142      */
143     private static final Map<String, NetconfDevice.SchemaResourcesDTO> SCHEMA_RESOURCES_DTO_MAP = new HashMap<>();
144
145     // Initializes default constant instances for the case when the default schema repository
146     // directory cache/schema is used.
147     static {
148         SCHEMA_RESOURCES_DTO_MAP.put(DEFAULT_CACHE_DIRECTORY,
149                 new NetconfDevice.SchemaResourcesDTO(DEFAULT_SCHEMA_REPOSITORY, DEFAULT_SCHEMA_REPOSITORY,
150                         DEFAULT_SCHEMA_CONTEXT_FACTORY,
151                         new NetconfStateSchemasResolverImpl()));
152         DEFAULT_SCHEMA_REPOSITORY.registerSchemaSourceListener(DEFAULT_CACHE);
153         DEFAULT_SCHEMA_REPOSITORY.registerSchemaSourceListener(
154                 TextToASTTransformer.create(DEFAULT_SCHEMA_REPOSITORY, DEFAULT_SCHEMA_REPOSITORY));
155     }
156
157     protected final String topologyId;
158     private final NetconfClientDispatcher clientDispatcher;
159     private final EventExecutor eventExecutor;
160     protected final ScheduledThreadPool keepaliveExecutor;
161     protected final ThreadPool processingExecutor;
162     protected final SharedSchemaRepository sharedSchemaRepository;
163     protected final DataBroker dataBroker;
164     protected final DOMMountPointService mountPointService;
165
166     protected SchemaSourceRegistry schemaRegistry = DEFAULT_SCHEMA_REPOSITORY;
167     protected SchemaRepository schemaRepository = DEFAULT_SCHEMA_REPOSITORY;
168     protected SchemaContextFactory schemaContextFactory = DEFAULT_SCHEMA_CONTEXT_FACTORY;
169
170     protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
171
172     protected final AAAEncryptionService encryptionService;
173
174     protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
175                                       final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
176                                       final ThreadPool processingExecutor,
177                                       final SchemaRepositoryProvider schemaRepositoryProvider,
178                                       final DataBroker dataBroker, final DOMMountPointService mountPointService,
179                                       final AAAEncryptionService encryptionService) {
180         this.topologyId = topologyId;
181         this.clientDispatcher = clientDispatcher;
182         this.eventExecutor = eventExecutor;
183         this.keepaliveExecutor = keepaliveExecutor;
184         this.processingExecutor = processingExecutor;
185         this.sharedSchemaRepository = schemaRepositoryProvider.getSharedSchemaRepository();
186         this.dataBroker = dataBroker;
187         this.mountPointService = mountPointService;
188         this.encryptionService = encryptionService;
189     }
190
191     public void setSchemaRegistry(final SchemaSourceRegistry schemaRegistry) {
192         this.schemaRegistry = schemaRegistry;
193     }
194
195     public void setSchemaContextFactory(final SchemaContextFactory schemaContextFactory) {
196         this.schemaContextFactory = schemaContextFactory;
197     }
198
199     @Override
200     public ListenableFuture<NetconfDeviceCapabilities> connectNode(final NodeId nodeId, final Node configNode) {
201         LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, configNode);
202         return setupConnection(nodeId, configNode);
203     }
204
205     @Override
206     public ListenableFuture<Void> disconnectNode(final NodeId nodeId) {
207         LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue());
208         if (!activeConnectors.containsKey(nodeId)) {
209             return Futures.immediateFailedFuture(
210                     new IllegalStateException("Unable to disconnect device that is not connected"));
211         }
212
213         // retrieve connection, and disconnect it
214         final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
215         connectorDTO.getCommunicator().close();
216         connectorDTO.getFacade().close();
217         return Futures.immediateFuture(null);
218     }
219
220     protected ListenableFuture<NetconfDeviceCapabilities> setupConnection(final NodeId nodeId,
221                                                                         final Node configNode) {
222         final NetconfNode netconfNode = configNode.getAugmentation(NetconfNode.class);
223
224         AuthEncryptor.encryptIfNeeded(nodeId, netconfNode, encryptionService, topologyId, dataBroker);
225
226         Preconditions.checkNotNull(netconfNode.getHost());
227         Preconditions.checkNotNull(netconfNode.getPort());
228         Preconditions.checkNotNull(netconfNode.isTcpOnly());
229
230         final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode);
231         final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
232         final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
233         final NetconfReconnectingClientConfiguration clientConfig =
234                 getClientConfig(netconfClientSessionListener, netconfNode);
235         final ListenableFuture<NetconfDeviceCapabilities> future =
236                 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
237
238         activeConnectors.put(nodeId, deviceCommunicatorDTO);
239
240         Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
241             @Override
242             public void onSuccess(final NetconfDeviceCapabilities result) {
243                 LOG.debug("Connector for : " + nodeId.getValue() + " started succesfully");
244             }
245
246             @Override
247             public void onFailure(final Throwable throwable) {
248                 LOG.error("Connector for : " + nodeId.getValue() + " failed");
249                 // remove this node from active connectors?
250             }
251         });
252
253         return future;
254     }
255
256     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId,
257                                                          final NetconfNode node) {
258         //setup default values since default value is not supported in mdsal
259         final Long defaultRequestTimeoutMillis = node.getDefaultRequestTimeoutMillis() == null
260                 ? DEFAULT_REQUEST_TIMEOUT_MILLIS : node.getDefaultRequestTimeoutMillis();
261         final Long keepaliveDelay = node.getKeepaliveDelay() == null
262                 ? DEFAULT_KEEPALIVE_DELAY : node.getKeepaliveDelay();
263         final Boolean reconnectOnChangedSchema = node.isReconnectOnChangedSchema() == null
264                 ? DEFAULT_RECONNECT_ON_CHANGED_SCHEMA : node.isReconnectOnChangedSchema();
265
266         final IpAddress ipAddress = node.getHost().getIpAddress();
267         final InetSocketAddress address = new InetSocketAddress(ipAddress.getIpv4Address() != null
268                 ? ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue(),
269                 node.getPort().getValue());
270         final RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address);
271
272         RemoteDeviceHandler<NetconfSessionPreferences> salFacade =
273                 createSalFacade(remoteDeviceId);
274
275         if (keepaliveDelay > 0) {
276             LOG.warn("Adding keepalive facade, for device {}", nodeId);
277             salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(),
278                     keepaliveDelay, defaultRequestTimeoutMillis);
279         }
280
281         // pre register yang library sources as fallback schemas to schema registry
282         final List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources = Lists.newArrayList();
283         if (node.getYangLibrary() != null) {
284             final String yangLibURL = node.getYangLibrary().getYangLibraryUrl().getValue();
285             final String yangLibUsername = node.getYangLibrary().getUsername();
286             final String yangLigPassword = node.getYangLibrary().getPassword();
287
288             final LibraryModulesSchemas libraryModulesSchemas;
289             if (yangLibURL != null) {
290                 if (yangLibUsername != null && yangLigPassword != null) {
291                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
292                 } else {
293                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL);
294                 }
295
296                 for (final Map.Entry<SourceIdentifier, URL> sourceIdentifierURLEntry
297                         : libraryModulesSchemas.getAvailableModels().entrySet()) {
298                     registeredYangLibSources
299                         .add(schemaRegistry.registerSchemaSource(
300                                 new YangLibrarySchemaYangSourceProvider(remoteDeviceId,
301                                         libraryModulesSchemas.getAvailableModels()),
302                                 PotentialSchemaSource.create(sourceIdentifierURLEntry.getKey(),
303                                         YangTextSchemaSource.class, PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
304                 }
305             }
306         }
307
308         final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node);
309         final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> device;
310         if (node.isSchemaless()) {
311             device = new SchemalessNetconfDevice(remoteDeviceId, salFacade);
312         } else {
313             device = new NetconfDeviceBuilder()
314                     .setReconnectOnSchemasChange(reconnectOnChangedSchema)
315                     .setSchemaResourcesDTO(schemaResourcesDTO)
316                     .setGlobalProcessingExecutor(processingExecutor.getExecutor())
317                     .setId(remoteDeviceId)
318                     .setSalFacade(salFacade)
319                     .build();
320         }
321
322         final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
323         final int rpcMessageLimit =
324                 node.getConcurrentRpcLimit() == null ? DEFAULT_CONCURRENT_RPC_LIMIT : node.getConcurrentRpcLimit();
325
326         if (rpcMessageLimit < 1) {
327             LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
328         }
329
330         return new NetconfConnectorDTO(userCapabilities.isPresent()
331                 ? new NetconfDeviceCommunicator(remoteDeviceId, device, userCapabilities.get(), rpcMessageLimit)
332                 : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit), salFacade);
333     }
334
335     protected NetconfDevice.SchemaResourcesDTO setupSchemaCacheDTO(final NodeId nodeId, final NetconfNode node) {
336         // Setup information related to the SchemaRegistry, SchemaResourceFactory, etc.
337         NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = null;
338         final String moduleSchemaCacheDirectory = node.getSchemaCacheDirectory();
339         // Only checks to ensure the String is not empty or null; further checks related to directory
340         // accessibility and file permissionsare handled during the FilesystemSchemaSourceCache initialization.
341         if (!Strings.isNullOrEmpty(moduleSchemaCacheDirectory)) {
342             // If a custom schema cache directory is specified, create the backing DTO; otherwise,
343             // the SchemaRegistry and SchemaContextFactory remain the default values.
344             if (!moduleSchemaCacheDirectory.equals(DEFAULT_CACHE_DIRECTORY)) {
345                 // Multiple modules may be created at once;
346                 // synchronize to avoid issues with data consistency among threads.
347                 synchronized (SCHEMA_RESOURCES_DTO_MAP) {
348                     // Look for the cached DTO to reuse SchemaRegistry and SchemaContextFactory variables
349                     // if they already exist
350                     schemaResourcesDTO = SCHEMA_RESOURCES_DTO_MAP.get(moduleSchemaCacheDirectory);
351                     if (schemaResourcesDTO == null) {
352                         schemaResourcesDTO = createSchemaResourcesDTO(moduleSchemaCacheDirectory);
353                         schemaResourcesDTO.getSchemaRegistry().registerSchemaSourceListener(
354                                 TextToASTTransformer.create((SchemaRepository) schemaResourcesDTO.getSchemaRegistry(),
355                                         schemaResourcesDTO.getSchemaRegistry())
356                         );
357                         SCHEMA_RESOURCES_DTO_MAP.put(moduleSchemaCacheDirectory, schemaResourcesDTO);
358                     }
359                 }
360                 LOG.info("Netconf connector for device {} will use schema cache directory {} instead of {}",
361                         nodeId.getValue(), moduleSchemaCacheDirectory, DEFAULT_CACHE_DIRECTORY);
362             }
363         } else {
364             LOG.warn("schema-cache-directory for {} is null or empty;  using the default {}",
365                     nodeId.getValue(), QUALIFIED_DEFAULT_CACHE_DIRECTORY);
366         }
367
368         if (schemaResourcesDTO == null) {
369             schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry, schemaRepository,
370                     schemaContextFactory, new NetconfStateSchemasResolverImpl());
371         }
372
373         return schemaResourcesDTO;
374     }
375
376     /**
377      * Creates the backing Schema classes for a particular directory.
378      *
379      * @param moduleSchemaCacheDirectory The string directory relative to "cache"
380      * @return A DTO containing the Schema classes for the Netconf mount.
381      */
382     private NetconfDevice.SchemaResourcesDTO createSchemaResourcesDTO(final String moduleSchemaCacheDirectory) {
383         final SharedSchemaRepository repository = new SharedSchemaRepository(moduleSchemaCacheDirectory);
384         final SchemaContextFactory schemaContextFactory
385                 = repository.createSchemaContextFactory(SchemaSourceFilter.ALWAYS_ACCEPT);
386         setSchemaRegistry(repository);
387         setSchemaContextFactory(schemaContextFactory);
388         final FilesystemSchemaSourceCache<YangTextSchemaSource> deviceCache =
389                 createDeviceFilesystemCache(moduleSchemaCacheDirectory);
390         repository.registerSchemaSourceListener(deviceCache);
391         return new NetconfDevice.SchemaResourcesDTO(repository, repository, schemaContextFactory,
392                 new NetconfStateSchemasResolverImpl());
393     }
394
395     /**
396      * Creates a <code>FilesystemSchemaSourceCache</code> for the custom schema cache directory.
397      *
398      * @param schemaCacheDirectory The custom cache directory relative to "cache"
399      * @return A <code>FilesystemSchemaSourceCache</code> for the custom schema cache directory
400      */
401     private FilesystemSchemaSourceCache<YangTextSchemaSource> createDeviceFilesystemCache(
402             final String schemaCacheDirectory) {
403         final String relativeSchemaCacheDirectory = CACHE_DIRECTORY + File.separator + schemaCacheDirectory;
404         return new FilesystemSchemaSourceCache<>(schemaRegistry, YangTextSchemaSource.class,
405                 new File(relativeSchemaCacheDirectory));
406     }
407
408     public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
409                                                                   final NetconfNode node) {
410
411         //setup default values since default value is not supported in mdsal
412         final long clientConnectionTimeoutMillis = node.getConnectionTimeoutMillis() == null
413                 ? DEFAULT_CONNECTION_TIMEOUT_MILLIS : node.getConnectionTimeoutMillis();
414         final long maxConnectionAttempts = node.getMaxConnectionAttempts() == null
415                 ? DEFAULT_MAX_CONNECTION_ATTEMPTS : node.getMaxConnectionAttempts();
416         final int betweenAttemptsTimeoutMillis = node.getBetweenAttemptsTimeoutMillis() == null
417                 ? DEFAULT_BETWEEN_ATTEMPTS_TIMEOUT_MILLIS : node.getBetweenAttemptsTimeoutMillis();
418         final BigDecimal sleepFactor = node.getSleepFactor() == null ? DEFAULT_SLEEP_FACTOR : node.getSleepFactor();
419
420         final InetSocketAddress socketAddress = getSocketAddress(node.getHost(), node.getPort().getValue());
421
422         final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
423                 maxConnectionAttempts, betweenAttemptsTimeoutMillis, sleepFactor);
424         final ReconnectStrategy strategy = sf.createReconnectStrategy();
425
426         final AuthenticationHandler authHandler;
427         final Credentials credentials = node.getCredentials();
428         if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114
429                 .netconf.node.credentials.credentials.LoginPassword) {
430             authHandler = new LoginPassword(
431                     ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114
432                             .netconf.node.credentials.credentials.LoginPassword) credentials).getUsername(),
433                     ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114
434                             .netconf.node.credentials.credentials.LoginPassword) credentials).getPassword(),
435                     encryptionService);
436         } else {
437             throw new IllegalStateException("Only login/password authentification is supported");
438         }
439
440         return NetconfReconnectingClientConfigurationBuilder.create()
441                 .withAddress(socketAddress)
442                 .withConnectionTimeoutMillis(clientConnectionTimeoutMillis)
443                 .withReconnectStrategy(strategy)
444                 .withAuthHandler(authHandler)
445                 .withProtocol(node.isTcpOnly() ? NetconfClientConfiguration.NetconfClientProtocol.TCP :
446                         NetconfClientConfiguration.NetconfClientProtocol.SSH)
447                 .withConnectStrategyFactory(sf)
448                 .withSessionListener(listener)
449                 .build();
450     }
451
452     protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
453
454     private InetSocketAddress getSocketAddress(final Host host, final int port) {
455         if (host.getDomainName() != null) {
456             return new InetSocketAddress(host.getDomainName().getValue(), port);
457         } else {
458             final IpAddress ipAddress = host.getIpAddress();
459             final String ip = ipAddress.getIpv4Address() != null
460                     ? ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue();
461             return new InetSocketAddress(ip, port);
462         }
463     }
464
465     private Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
466         // if none of yang-module-capabilities or non-module-capabilities is specified
467         // just return absent
468         if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
469             return Optional.absent();
470         }
471
472         final List<String> capabilities = new ArrayList<>();
473
474         boolean overrideYangModuleCaps = false;
475         if (node.getYangModuleCapabilities() != null) {
476             capabilities.addAll(node.getYangModuleCapabilities().getCapability());
477             overrideYangModuleCaps = node.getYangModuleCapabilities().isOverride();
478         }
479
480         //non-module capabilities should not exist in yang module capabilities
481         final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
482         Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
483                 "List yang-module-capabilities/capability should contain only module based capabilities. "
484                         + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
485
486         boolean overrideNonModuleCaps = false;
487         if (node.getNonModuleCapabilities() != null) {
488             capabilities.addAll(node.getNonModuleCapabilities().getCapability());
489             overrideNonModuleCaps = node.getNonModuleCapabilities().isOverride();
490         }
491
492         return Optional.of(new UserPreferences(NetconfSessionPreferences
493             .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));
494     }
495
496     private static final class TimedReconnectStrategyFactory implements ReconnectStrategyFactory {
497         private final Long connectionAttempts;
498         private final EventExecutor executor;
499         private final double sleepFactor;
500         private final int minSleep;
501
502         TimedReconnectStrategyFactory(final EventExecutor executor, final Long maxConnectionAttempts,
503                                       final int minSleep, final BigDecimal sleepFactor) {
504             if (maxConnectionAttempts != null && maxConnectionAttempts > 0) {
505                 connectionAttempts = maxConnectionAttempts;
506             } else {
507                 connectionAttempts = null;
508             }
509
510             this.sleepFactor = sleepFactor.doubleValue();
511             this.executor = executor;
512             this.minSleep = minSleep;
513         }
514
515         @Override
516         public ReconnectStrategy createReconnectStrategy() {
517             final Long maxSleep = null;
518             final Long deadline = null;
519
520             return new TimedReconnectStrategy(executor, minSleep,
521                     minSleep, sleepFactor, maxSleep, connectionAttempts, deadline);
522         }
523     }
524
525     protected static class NetconfConnectorDTO implements AutoCloseable {
526
527         private final NetconfDeviceCommunicator communicator;
528         private final RemoteDeviceHandler<NetconfSessionPreferences> facade;
529
530         public NetconfConnectorDTO(final NetconfDeviceCommunicator communicator,
531                                    final RemoteDeviceHandler<NetconfSessionPreferences> facade) {
532             this.communicator = communicator;
533             this.facade = facade;
534         }
535
536         public NetconfDeviceCommunicator getCommunicator() {
537             return communicator;
538         }
539
540         public RemoteDeviceHandler<NetconfSessionPreferences> getFacade() {
541             return facade;
542         }
543
544         public NetconfClientSessionListener getSessionListener() {
545             return communicator;
546         }
547
548         @Override
549         public void close() {
550             communicator.close();
551             facade.close();
552         }
553     }
554 }