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