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