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