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