Prepare netconf to support YANG 1.1 actions
[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 : " + nodeId.getValue() + " started succesfully");
294             }
295
296             @Override
297             public void onFailure(final Throwable throwable) {
298                 LOG.error("Connector for : " + nodeId.getValue() + " failed");
299                 // remove this node from active connectors?
300             }
301         }, MoreExecutors.directExecutor());
302
303         return future;
304     }
305
306     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId,
307                                                            final NetconfNode node) {
308         //setup default values since default value is not supported in mdsal
309         final long defaultRequestTimeoutMillis = node.getDefaultRequestTimeoutMillis() == null
310                 ? DEFAULT_REQUEST_TIMEOUT_MILLIS : node.getDefaultRequestTimeoutMillis();
311         final long keepaliveDelay = node.getKeepaliveDelay() == null
312                 ? DEFAULT_KEEPALIVE_DELAY : node.getKeepaliveDelay();
313         final boolean reconnectOnChangedSchema = node.isReconnectOnChangedSchema() == null
314                 ? DEFAULT_RECONNECT_ON_CHANGED_SCHEMA : node.isReconnectOnChangedSchema();
315
316         final IpAddress ipAddress = node.getHost().getIpAddress();
317         final InetSocketAddress address = new InetSocketAddress(ipAddress.getIpv4Address() != null
318                 ? ipAddress.getIpv4Address().getValue() : ipAddress.getIpv6Address().getValue(),
319                 node.getPort().getValue());
320         final RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address);
321
322         RemoteDeviceHandler<NetconfSessionPreferences> salFacade =
323                 createSalFacade(remoteDeviceId);
324
325         if (keepaliveDelay > 0) {
326             LOG.warn("Adding keepalive facade, for device {}", nodeId);
327             salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, this.keepaliveExecutor.getExecutor(),
328                     keepaliveDelay, defaultRequestTimeoutMillis);
329         }
330
331         // pre register yang library sources as fallback schemas to schema registry
332         final List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources = Lists.newArrayList();
333         if (node.getYangLibrary() != null) {
334             final String yangLibURL = node.getYangLibrary().getYangLibraryUrl().getValue();
335             final String yangLibUsername = node.getYangLibrary().getUsername();
336             final String yangLigPassword = node.getYangLibrary().getPassword();
337
338             final LibraryModulesSchemas libraryModulesSchemas;
339             if (yangLibURL != null) {
340                 if (yangLibUsername != null && yangLigPassword != null) {
341                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
342                 } else {
343                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL);
344                 }
345
346                 for (final Map.Entry<SourceIdentifier, URL> sourceIdentifierURLEntry
347                         : libraryModulesSchemas.getAvailableModels().entrySet()) {
348                     registeredYangLibSources
349                         .add(schemaRegistry.registerSchemaSource(
350                                 new YangLibrarySchemaYangSourceProvider(remoteDeviceId,
351                                         libraryModulesSchemas.getAvailableModels()),
352                                 PotentialSchemaSource.create(sourceIdentifierURLEntry.getKey(),
353                                         YangTextSchemaSource.class, PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
354                 }
355             }
356         }
357
358         final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node);
359         final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> device;
360         if (node.isSchemaless()) {
361             device = new SchemalessNetconfDevice(remoteDeviceId, salFacade);
362         } else {
363             NetconfDeviceBuilder netconfDeviceBuilder = new NetconfDeviceBuilder()
364                     .setReconnectOnSchemasChange(reconnectOnChangedSchema)
365                     .setSchemaResourcesDTO(schemaResourcesDTO)
366                     .setGlobalProcessingExecutor(this.processingExecutor.getExecutor())
367                     .setId(remoteDeviceId)
368                     .setSalFacade(salFacade);
369             if (this.deviceActionFactory != null) {
370                 netconfDeviceBuilder.setDeviceActionFactory(this.deviceActionFactory);
371             }
372             device = netconfDeviceBuilder.build();
373         }
374
375         final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
376         final int rpcMessageLimit =
377                 node.getConcurrentRpcLimit() == null ? DEFAULT_CONCURRENT_RPC_LIMIT : node.getConcurrentRpcLimit();
378
379         if (rpcMessageLimit < 1) {
380             LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
381         }
382
383         NetconfDeviceCommunicator netconfDeviceCommunicator =
384              userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
385                      userCapabilities.get(), rpcMessageLimit)
386             : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
387
388         if (salFacade instanceof KeepaliveSalFacade) {
389             ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
390         }
391         return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade);
392     }
393
394     protected NetconfDevice.SchemaResourcesDTO setupSchemaCacheDTO(final NodeId nodeId, final NetconfNode node) {
395         // Setup information related to the SchemaRegistry, SchemaResourceFactory, etc.
396         NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = null;
397         final String moduleSchemaCacheDirectory = node.getSchemaCacheDirectory();
398         // Only checks to ensure the String is not empty or null; further checks related to directory
399         // accessibility and file permissionsare handled during the FilesystemSchemaSourceCache initialization.
400         if (!Strings.isNullOrEmpty(moduleSchemaCacheDirectory)) {
401             // If a custom schema cache directory is specified, create the backing DTO; otherwise,
402             // the SchemaRegistry and SchemaContextFactory remain the default values.
403             if (!moduleSchemaCacheDirectory.equals(DEFAULT_CACHE_DIRECTORY)) {
404                 // Multiple modules may be created at once;
405                 // synchronize to avoid issues with data consistency among threads.
406                 synchronized (SCHEMA_RESOURCES_DTO_MAP) {
407                     // Look for the cached DTO to reuse SchemaRegistry and SchemaContextFactory variables
408                     // if they already exist
409                     schemaResourcesDTO = SCHEMA_RESOURCES_DTO_MAP.get(moduleSchemaCacheDirectory);
410                     if (schemaResourcesDTO == null) {
411                         schemaResourcesDTO = createSchemaResourcesDTO(moduleSchemaCacheDirectory);
412                         schemaResourcesDTO.getSchemaRegistry().registerSchemaSourceListener(
413                                 TextToASTTransformer.create((SchemaRepository) schemaResourcesDTO.getSchemaRegistry(),
414                                         schemaResourcesDTO.getSchemaRegistry())
415                         );
416                         SCHEMA_RESOURCES_DTO_MAP.put(moduleSchemaCacheDirectory, schemaResourcesDTO);
417                     }
418                 }
419                 LOG.info("Netconf connector for device {} will use schema cache directory {} instead of {}",
420                         nodeId.getValue(), moduleSchemaCacheDirectory, DEFAULT_CACHE_DIRECTORY);
421             }
422         } else {
423             LOG.warn("schema-cache-directory for {} is null or empty;  using the default {}",
424                     nodeId.getValue(), QUALIFIED_DEFAULT_CACHE_DIRECTORY);
425         }
426
427         if (schemaResourcesDTO == null) {
428             schemaResourcesDTO = new NetconfDevice.SchemaResourcesDTO(schemaRegistry, schemaRepository,
429                     schemaContextFactory, new NetconfStateSchemasResolverImpl());
430         }
431
432         return schemaResourcesDTO;
433     }
434
435     /**
436      * Creates the backing Schema classes for a particular directory.
437      *
438      * @param moduleSchemaCacheDirectory The string directory relative to "cache"
439      * @return A DTO containing the Schema classes for the Netconf mount.
440      */
441     private NetconfDevice.SchemaResourcesDTO createSchemaResourcesDTO(final String moduleSchemaCacheDirectory) {
442         final SharedSchemaRepository repository = new SharedSchemaRepository(moduleSchemaCacheDirectory);
443         final SchemaContextFactory contextFactory
444                 = repository.createSchemaContextFactory(SchemaSourceFilter.ALWAYS_ACCEPT);
445         setSchemaRegistry(repository);
446         setSchemaContextFactory(contextFactory);
447         final FilesystemSchemaSourceCache<YangTextSchemaSource> deviceCache =
448                 createDeviceFilesystemCache(moduleSchemaCacheDirectory);
449         repository.registerSchemaSourceListener(deviceCache);
450         repository.registerSchemaSourceListener(
451             InMemorySchemaSourceCache.createSoftCache(repository, ASTSchemaSource.class));
452         return new NetconfDevice.SchemaResourcesDTO(repository, repository, contextFactory,
453                 new NetconfStateSchemasResolverImpl());
454     }
455
456     /**
457      * Creates a <code>FilesystemSchemaSourceCache</code> for the custom schema cache directory.
458      *
459      * @param schemaCacheDirectory The custom cache directory relative to "cache"
460      * @return A <code>FilesystemSchemaSourceCache</code> for the custom schema cache directory
461      */
462     private FilesystemSchemaSourceCache<YangTextSchemaSource> createDeviceFilesystemCache(
463             final String schemaCacheDirectory) {
464         final String relativeSchemaCacheDirectory = CACHE_DIRECTORY + File.separator + schemaCacheDirectory;
465         return new FilesystemSchemaSourceCache<>(schemaRegistry, YangTextSchemaSource.class,
466                 new File(relativeSchemaCacheDirectory));
467     }
468
469     /**
470      * Sets the private key path from location specified in configuration file using blueprint.
471      */
472     public void setPrivateKeyPath(final String privateKeyPath) {
473         this.privateKeyPath = privateKeyPath;
474     }
475
476     /**
477      * Sets the private key passphrase from location specified in configuration file using blueprint.
478      */
479     public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
480         this.privateKeyPassphrase = privateKeyPassphrase;
481     }
482
483     public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
484                                                                   final NetconfNode node) {
485
486         //setup default values since default value is not supported in mdsal
487         final long clientConnectionTimeoutMillis = node.getConnectionTimeoutMillis() == null
488                 ? DEFAULT_CONNECTION_TIMEOUT_MILLIS : node.getConnectionTimeoutMillis();
489         final long maxConnectionAttempts = node.getMaxConnectionAttempts() == null
490                 ? DEFAULT_MAX_CONNECTION_ATTEMPTS : node.getMaxConnectionAttempts();
491         final int betweenAttemptsTimeoutMillis = node.getBetweenAttemptsTimeoutMillis() == null
492                 ? DEFAULT_BETWEEN_ATTEMPTS_TIMEOUT_MILLIS : node.getBetweenAttemptsTimeoutMillis();
493         final BigDecimal sleepFactor = node.getSleepFactor() == null ? DEFAULT_SLEEP_FACTOR : node.getSleepFactor();
494
495         final InetSocketAddress socketAddress = getSocketAddress(node.getHost(), node.getPort().getValue());
496
497         final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
498                 maxConnectionAttempts, betweenAttemptsTimeoutMillis, sleepFactor);
499         final ReconnectStrategy strategy = sf.createReconnectStrategy();
500
501         final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder =
502                 NetconfReconnectingClientConfigurationBuilder.create();
503
504         if (node.isTcpOnly() || node.getProtocol() == null || node.getProtocol().getName() == Name.SSH) {
505             final AuthenticationHandler authHandler = getHandlerFromCredentials(node.getCredentials());
506             reconnectingClientConfigurationBuilder
507                 .withAuthHandler(authHandler)
508                 .withProtocol(node.isTcpOnly() ? NetconfClientConfiguration.NetconfClientProtocol.TCP :
509                     NetconfClientConfiguration.NetconfClientProtocol.SSH);
510         } else if (node.getProtocol().getName() == Name.TLS) {
511             final SslHandlerFactory sslHandlerFactory = new SslHandlerFactoryImpl(keystoreAdapter,
512                     node.getProtocol().getSpecification());
513             reconnectingClientConfigurationBuilder
514                 .withSslHandlerFactory(sslHandlerFactory)
515                 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
516         } else {
517             throw new IllegalStateException("Unsupported protocol type: " + node.getProtocol().getName().getClass());
518         }
519
520         return reconnectingClientConfigurationBuilder
521                 .withAddress(socketAddress)
522                 .withConnectionTimeoutMillis(clientConnectionTimeoutMillis)
523                 .withReconnectStrategy(strategy)
524                 .withConnectStrategyFactory(sf)
525                 .withSessionListener(listener)
526                 .build();
527     }
528
529     private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
530         if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
531                 .rev150114.netconf.node.credentials.credentials.LoginPassword) {
532             final org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
533                     .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword
534                     = (org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
535                     .rev150114.netconf.node.credentials.credentials.LoginPassword) credentials;
536             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
537         }
538         if (credentials instanceof LoginPwUnencrypted) {
539             final LoginPasswordUnencrypted loginPassword =
540                     ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
541             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
542         }
543         if (credentials instanceof LoginPw) {
544             final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
545             return new LoginPasswordHandler(loginPassword.getUsername(),
546                     encryptionService.decrypt(loginPassword.getPassword()));
547         }
548         if (credentials instanceof KeyAuth) {
549             final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
550             return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
551                     keystoreAdapter, encryptionService);
552         }
553         throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
554     }
555
556     protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
557
558     private static InetSocketAddress getSocketAddress(final Host host, final int port) {
559         if (host.getDomainName() != null) {
560             return new InetSocketAddress(host.getDomainName().getValue(), port);
561         }
562
563         final IpAddress ipAddress = host.getIpAddress();
564         final String ip = ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue()
565                 : ipAddress.getIpv6Address().getValue();
566         return new InetSocketAddress(ip, port);
567     }
568
569     private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
570         // if none of yang-module-capabilities or non-module-capabilities is specified
571         // just return absent
572         if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
573             return Optional.absent();
574         }
575
576         final List<String> capabilities = new ArrayList<>();
577
578         boolean overrideYangModuleCaps = false;
579         if (node.getYangModuleCapabilities() != null) {
580             capabilities.addAll(node.getYangModuleCapabilities().getCapability());
581             overrideYangModuleCaps = node.getYangModuleCapabilities().isOverride();
582         }
583
584         //non-module capabilities should not exist in yang module capabilities
585         final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
586         Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
587                 "List yang-module-capabilities/capability should contain only module based capabilities. "
588                         + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
589
590         boolean overrideNonModuleCaps = false;
591         if (node.getNonModuleCapabilities() != null) {
592             capabilities.addAll(node.getNonModuleCapabilities().getCapability());
593             overrideNonModuleCaps = node.getNonModuleCapabilities().isOverride();
594         }
595
596         return Optional.of(new UserPreferences(NetconfSessionPreferences
597             .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));
598     }
599
600     private static final class TimedReconnectStrategyFactory implements ReconnectStrategyFactory {
601         private final Long connectionAttempts;
602         private final EventExecutor executor;
603         private final double sleepFactor;
604         private final int minSleep;
605
606         TimedReconnectStrategyFactory(final EventExecutor executor, final Long maxConnectionAttempts,
607                                       final int minSleep, final BigDecimal sleepFactor) {
608             if (maxConnectionAttempts != null && maxConnectionAttempts > 0) {
609                 connectionAttempts = maxConnectionAttempts;
610             } else {
611                 connectionAttempts = null;
612             }
613
614             this.sleepFactor = sleepFactor.doubleValue();
615             this.executor = executor;
616             this.minSleep = minSleep;
617         }
618
619         @Override
620         public ReconnectStrategy createReconnectStrategy() {
621             return new TimedReconnectStrategy(executor, minSleep,
622                     minSleep, sleepFactor, null /*maxSleep*/, connectionAttempts, null /*deadline*/);
623         }
624     }
625
626     protected static class NetconfConnectorDTO implements AutoCloseable {
627
628         private final NetconfDeviceCommunicator communicator;
629         private final RemoteDeviceHandler<NetconfSessionPreferences> facade;
630
631         public NetconfConnectorDTO(final NetconfDeviceCommunicator communicator,
632                                    final RemoteDeviceHandler<NetconfSessionPreferences> facade) {
633             this.communicator = communicator;
634             this.facade = facade;
635         }
636
637         public NetconfDeviceCommunicator getCommunicator() {
638             return communicator;
639         }
640
641         public RemoteDeviceHandler<NetconfSessionPreferences> getFacade() {
642             return facade;
643         }
644
645         public NetconfClientSessionListener getSessionListener() {
646             return communicator;
647         }
648
649         @Override
650         public void close() {
651             communicator.close();
652             facade.close();
653         }
654     }
655
656     private static final class SslHandlerFactoryImpl implements SslHandlerFactory {
657         private final NetconfKeystoreAdapter keystoreAdapter;
658         private final Optional<Specification> specOptional;
659
660         SslHandlerFactoryImpl(final NetconfKeystoreAdapter keystoreAdapter, final Specification specification) {
661             this.keystoreAdapter = keystoreAdapter;
662             this.specOptional = Optional.fromNullable(specification);
663         }
664
665         @Override
666         public SslHandler createSslHandler() {
667             try {
668                 final KeyStore keyStore = keystoreAdapter.getJavaKeyStore();
669
670                 final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
671                 kmf.init(keyStore, "".toCharArray());
672
673                 final TrustManagerFactory tmf =
674                         TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
675                 tmf.init(keyStore);
676
677                 final SSLContext sslCtx = SSLContext.getInstance("TLS");
678                 sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
679                 final SSLEngine engine = sslCtx.createSSLEngine();
680                 engine.setUseClientMode(true);
681
682                 final Set<String> protocols = Sets.newHashSet(engine.getSupportedProtocols());
683                 if (specOptional.isPresent()) {
684                     final Specification specification = specOptional.get();
685                     if (!(specification instanceof TlsCase)) {
686                         throw new IllegalArgumentException("Cannot get TLS specification from: " + specification);
687                     }
688                     protocols.removeAll(((TlsCase)specification).getTls().getExcludedVersions());
689                 }
690
691                 engine.setEnabledProtocols(protocols.toArray(new String[0]));
692                 engine.setEnabledCipherSuites(engine.getSupportedCipherSuites());
693                 engine.setEnableSessionCreation(true);
694
695                 return new SslHandler(engine);
696             } catch (GeneralSecurityException | IOException exc) {
697                 throw new IllegalStateException(exc);
698             }
699         }
700     }
701 }