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
8 package org.opendaylight.netconf.topology.spi;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.base.Preconditions;
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.ListeningExecutorService;
19 import com.google.common.util.concurrent.MoreExecutors;
20 import io.netty.util.concurrent.EventExecutor;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
26 import java.util.Optional;
27 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
28 import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
29 import org.opendaylight.controller.config.threadpool.ThreadPool;
30 import org.opendaylight.mdsal.binding.api.DataBroker;
31 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
32 import org.opendaylight.netconf.client.NetconfClientDispatcher;
33 import org.opendaylight.netconf.client.NetconfClientSessionListener;
34 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
35 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
36 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
37 import org.opendaylight.netconf.nettyutil.ReconnectStrategyFactory;
38 import org.opendaylight.netconf.nettyutil.TimedReconnectStrategyFactory;
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.DeviceActionFactory;
42 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
43 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
44 import org.opendaylight.netconf.sal.connect.api.SchemaResourceManager;
45 import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
46 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice.SchemaResourcesDTO;
47 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
48 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
49 import org.opendaylight.netconf.sal.connect.netconf.auth.DatastoreBackedPublicKeyAuth;
50 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
51 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
52 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
53 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
54 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
55 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
56 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
57 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseNetconfSchemas;
58 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
59 import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
60 import org.opendaylight.netconf.topology.api.NetconfTopology;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
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.opendaylight.netconf.node.topology.rev150114.netconf.schema.storage.YangLibrary;
75 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
76 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
77 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
78 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
79 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
80 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
81 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
85 public abstract class AbstractNetconfTopology implements NetconfTopology {
86 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
88 private final NetconfClientDispatcher clientDispatcher;
89 private final EventExecutor eventExecutor;
90 private final DeviceActionFactory deviceActionFactory;
91 private final NetconfKeystoreAdapter keystoreAdapter;
92 private final SchemaResourceManager schemaManager;
93 private final BaseNetconfSchemas baseSchemas;
95 protected final ScheduledThreadPool keepaliveExecutor;
96 protected final ListeningExecutorService processingExecutor;
97 protected final DataBroker dataBroker;
98 protected final DOMMountPointService mountPointService;
99 protected final String topologyId;
100 protected String privateKeyPath;
101 protected String privateKeyPassphrase;
102 protected final AAAEncryptionService encryptionService;
103 protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
105 protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
106 final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
107 final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
108 final DataBroker dataBroker, final DOMMountPointService mountPointService,
109 final AAAEncryptionService encryptionService,
110 final DeviceActionFactory deviceActionFactory,
111 final BaseNetconfSchemas baseSchemas) {
112 this.topologyId = topologyId;
113 this.clientDispatcher = clientDispatcher;
114 this.eventExecutor = eventExecutor;
115 this.keepaliveExecutor = keepaliveExecutor;
116 this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
117 this.schemaManager = requireNonNull(schemaManager);
118 this.deviceActionFactory = deviceActionFactory;
119 this.dataBroker = dataBroker;
120 this.mountPointService = mountPointService;
121 this.encryptionService = encryptionService;
122 this.baseSchemas = requireNonNull(baseSchemas);
124 keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
128 public ListenableFuture<NetconfDeviceCapabilities> connectNode(final NodeId nodeId, final Node configNode) {
129 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
130 return setupConnection(nodeId, configNode);
134 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
136 * @param nodeConfiguration Node configuration container.
137 * @return String representation of node configuration with credentials replaced by asterisks.
140 public static String hideCredentials(final Node nodeConfiguration) {
141 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
142 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
143 final String nodeConfigurationString = nodeConfiguration.toString();
144 return nodeConfigurationString.replace(nodeCredentials, "***");
148 public ListenableFuture<Void> disconnectNode(final NodeId nodeId) {
149 LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue());
151 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
152 if (connectorDTO == null) {
153 return Futures.immediateFailedFuture(
154 new IllegalStateException("Unable to disconnect device that is not connected"));
157 connectorDTO.close();
158 return Futures.immediateFuture(null);
161 protected ListenableFuture<NetconfDeviceCapabilities> setupConnection(final NodeId nodeId,
162 final Node configNode) {
163 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
164 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
166 requireNonNull(netconfNode.getHost());
167 requireNonNull(netconfNode.getPort());
169 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
170 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
171 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
172 final NetconfReconnectingClientConfiguration clientConfig =
173 getClientConfig(netconfClientSessionListener, netconfNode, nodeId);
174 final ListenableFuture<NetconfDeviceCapabilities> future =
175 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
177 activeConnectors.put(nodeId, deviceCommunicatorDTO);
179 Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
181 public void onSuccess(final NetconfDeviceCapabilities result) {
182 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
186 public void onFailure(final Throwable throwable) {
187 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
188 // remove this node from active connectors?
190 }, MoreExecutors.directExecutor());
195 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
196 return createDeviceCommunicator(nodeId, node, null);
199 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
200 final NetconfNodeAugmentedOptional nodeOptional) {
201 final RemoteDeviceId remoteDeviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
203 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
204 RemoteDeviceHandler<NetconfSessionPreferences> salFacade = createSalFacade(remoteDeviceId);
205 if (keepaliveDelay > 0) {
206 LOG.info("Adding keepalive facade, for device {}", nodeId);
207 salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(),
208 keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
211 final RemoteDevice<NetconfSessionPreferences, NetconfDeviceCommunicator> device;
212 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
213 if (node.requireSchemaless()) {
214 device = new SchemalessNetconfDevice(baseSchemas, remoteDeviceId, salFacade);
215 yanglibRegistrations = List.of();
217 final boolean reconnectOnChangedSchema = node.requireReconnectOnChangedSchema();
218 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(),
220 device = new NetconfDeviceBuilder()
221 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
222 .setSchemaResourcesDTO(resources)
223 .setGlobalProcessingExecutor(processingExecutor)
224 .setId(remoteDeviceId)
225 .setSalFacade(salFacade)
227 .setEventExecutor(eventExecutor)
228 .setNodeOptional(nodeOptional)
229 .setDeviceActionFactory(deviceActionFactory)
230 .setBaseSchemas(baseSchemas)
232 yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources);
235 final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
236 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
237 if (rpcMessageLimit < 1) {
238 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
241 final NetconfDeviceCommunicator netconfDeviceCommunicator =
242 userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
243 userCapabilities.get(), rpcMessageLimit)
244 : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
246 if (salFacade instanceof KeepaliveSalFacade) {
247 ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
250 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
253 private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
254 final NetconfNode node, final SchemaResourcesDTO resources) {
255 final YangLibrary yangLibrary = node.getYangLibrary();
256 if (yangLibrary != null) {
257 final Uri uri = yangLibrary.getYangLibraryUrl();
259 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
260 final String yangLibURL = uri.getValue();
261 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
263 // pre register yang library sources as fallback schemas to schema registry
264 final LibraryModulesSchemas schemas;
265 final String yangLibUsername = yangLibrary.getUsername();
266 final String yangLigPassword = yangLibrary.getPassword();
267 if (yangLibUsername != null && yangLigPassword != null) {
268 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
270 schemas = LibraryModulesSchemas.create(yangLibURL);
273 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
274 registrations.add(schemaRegistry.registerSchemaSource(new YangLibrarySchemaYangSourceProvider(
275 remoteDeviceId, schemas.getAvailableModels()),
276 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
277 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
279 return List.copyOf(registrations);
287 * Sets the private key path from location specified in configuration file using blueprint.
289 public void setPrivateKeyPath(final String privateKeyPath) {
290 this.privateKeyPath = privateKeyPath;
294 * Sets the private key passphrase from location specified in configuration file using blueprint.
296 public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
297 this.privateKeyPassphrase = privateKeyPassphrase;
300 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
301 final NetconfNode node, final NodeId nodeId) {
302 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
303 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
304 node.requireSleepFactor().decimalValue());
305 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
306 final Protocol protocol = node.getProtocol();
307 if (node.requireTcpOnly()) {
308 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
309 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
310 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
311 } else if (protocol == null || protocol.getName() == Name.SSH) {
312 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
313 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
314 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
315 } else if (protocol.getName() == Name.TLS) {
316 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
317 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
318 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
320 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
323 if (node.getOdlHelloMessageCapabilities() != null) {
324 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
325 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
328 return reconnectingClientConfigurationBuilder
329 .withName(nodeId.getValue())
330 .withAddress(NetconfNodeUtils.toInetSocketAddress(node))
331 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
332 .withReconnectStrategy(sf.createReconnectStrategy())
333 .withConnectStrategyFactory(sf)
334 .withSessionListener(listener)
338 private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
340 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
341 .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword) {
342 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
344 if (credentials instanceof LoginPwUnencrypted) {
345 final LoginPasswordUnencrypted loginPassword =
346 ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
347 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
349 if (credentials instanceof LoginPw) {
350 final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
351 return new LoginPasswordHandler(loginPassword.getUsername(),
352 encryptionService.decrypt(loginPassword.getPassword()));
354 if (credentials instanceof KeyAuth) {
355 final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
356 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
357 keystoreAdapter, encryptionService);
359 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
362 protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
364 private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
365 // if none of yang-module-capabilities or non-module-capabilities is specified
366 // just return absent
367 if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
368 return Optional.empty();
371 final List<String> capabilities = new ArrayList<>();
373 boolean overrideYangModuleCaps = false;
374 if (node.getYangModuleCapabilities() != null) {
375 capabilities.addAll(node.getYangModuleCapabilities().getCapability());
376 overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
379 //non-module capabilities should not exist in yang module capabilities
380 final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
381 Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
382 "List yang-module-capabilities/capability should contain only module based capabilities. "
383 + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
385 boolean overrideNonModuleCaps = false;
386 if (node.getNonModuleCapabilities() != null) {
387 capabilities.addAll(node.getNonModuleCapabilities().getCapability());
388 overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
391 return Optional.of(new UserPreferences(NetconfSessionPreferences
392 .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));