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