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