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