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