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