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