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.collect.Lists;
14 import com.google.common.util.concurrent.FutureCallback;
15 import com.google.common.util.concurrent.Futures;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import com.google.common.util.concurrent.ListeningExecutorService;
18 import com.google.common.util.concurrent.MoreExecutors;
19 import io.netty.util.concurrent.EventExecutor;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
25 import java.util.concurrent.ExecutionException;
26 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
27 import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
28 import org.opendaylight.controller.config.threadpool.ThreadPool;
29 import org.opendaylight.mdsal.binding.api.DataBroker;
30 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
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.client.mdsal.DatastoreBackedPublicKeyAuth;
38 import org.opendaylight.netconf.client.mdsal.LibraryModulesSchemas;
39 import org.opendaylight.netconf.client.mdsal.LibrarySchemaSourceProvider;
40 import org.opendaylight.netconf.client.mdsal.NetconfDevice.SchemaResourcesDTO;
41 import org.opendaylight.netconf.client.mdsal.NetconfDeviceBuilder;
42 import org.opendaylight.netconf.client.mdsal.NetconfDeviceCommunicator;
43 import org.opendaylight.netconf.client.mdsal.SchemalessNetconfDevice;
44 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
45 import org.opendaylight.netconf.client.mdsal.api.CredentialProvider;
46 import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
47 import org.opendaylight.netconf.client.mdsal.api.RemoteDevice;
48 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceHandler;
49 import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
50 import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
51 import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
52 import org.opendaylight.netconf.nettyutil.ReconnectStrategyFactory;
53 import org.opendaylight.netconf.nettyutil.TimedReconnectStrategyFactory;
54 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
55 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
56 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
57 import org.opendaylight.netconf.topology.api.NetconfTopology;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev230430.connection.parameters.Protocol.Name;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev230430.credentials.Credentials;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev230430.credentials.credentials.KeyAuth;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev230430.credentials.credentials.LoginPw;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev230430.credentials.credentials.LoginPwUnencrypted;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev221225.NetconfNodeAugmentedOptional;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode;
66 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
67 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
68 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
69 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
70 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
71 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
72 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
73 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
74 import org.opendaylight.yangtools.yang.common.Empty;
75 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
76 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
77 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
78 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
79 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
83 public abstract class AbstractNetconfTopology implements NetconfTopology {
84 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
86 private final NetconfClientDispatcher clientDispatcher;
87 private final EventExecutor eventExecutor;
88 private final DeviceActionFactory deviceActionFactory;
89 private final CredentialProvider credentialProvider;
90 private final SslHandlerFactoryProvider sslHandlerFactoryProvider;
91 private final SchemaResourceManager schemaManager;
92 private final BaseNetconfSchemas baseSchemas;
94 protected final ScheduledThreadPool keepaliveExecutor;
95 protected final ListeningExecutorService processingExecutor;
96 protected final DataBroker dataBroker;
97 protected final DOMMountPointService mountPointService;
98 protected final String topologyId;
99 protected final AAAEncryptionService encryptionService;
100 protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
102 protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
103 final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
104 final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
105 final DataBroker dataBroker, final DOMMountPointService mountPointService,
106 final AAAEncryptionService encryptionService,
107 final DeviceActionFactory deviceActionFactory,
108 final BaseNetconfSchemas baseSchemas,
109 final CredentialProvider credentialProvider,
110 final SslHandlerFactoryProvider sslHandlerFactoryProvider) {
111 this.topologyId = requireNonNull(topologyId);
112 this.clientDispatcher = clientDispatcher;
113 this.eventExecutor = eventExecutor;
114 this.keepaliveExecutor = keepaliveExecutor;
115 this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
116 this.schemaManager = requireNonNull(schemaManager);
117 this.deviceActionFactory = deviceActionFactory;
118 this.dataBroker = requireNonNull(dataBroker);
119 this.mountPointService = mountPointService;
120 this.encryptionService = encryptionService;
121 this.baseSchemas = requireNonNull(baseSchemas);
122 this.credentialProvider = requireNonNull(credentialProvider);
123 this.sslHandlerFactoryProvider = requireNonNull(sslHandlerFactoryProvider);
125 // FIXME: this should be a put(), as we are initializing and will be re-populating the datastore with all the
126 // devices. Whatever has been there before should be nuked to properly re-align lifecycle.
127 final var wtx = dataBroker.newWriteOnlyTransaction();
128 wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
129 .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
130 .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
131 final var future = wtx.commit();
134 } catch (InterruptedException | ExecutionException e) {
135 LOG.error("Unable to initialize topology {}", topologyId, e);
136 throw new IllegalStateException(e);
139 LOG.debug("Topology {} initialized", topologyId);
143 public ListenableFuture<Empty> connectNode(final NodeId nodeId, final Node configNode) {
144 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
145 return setupConnection(nodeId, configNode);
149 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
151 * @param nodeConfiguration Node configuration container.
152 * @return String representation of node configuration with credentials replaced by asterisks.
155 public static String hideCredentials(final Node nodeConfiguration) {
156 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
157 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
158 final String nodeConfigurationString = nodeConfiguration.toString();
159 return nodeConfigurationString.replace(nodeCredentials, "***");
163 public ListenableFuture<Empty> disconnectNode(final NodeId nodeId) {
164 final var nodeName = nodeId.getValue();
165 LOG.debug("Disconnecting RemoteDevice{{}}", nodeName);
167 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
168 if (connectorDTO == null) {
169 return Futures.immediateFailedFuture(
170 new IllegalStateException("Cannot disconnect " + nodeName + " as it is not connected"));
173 connectorDTO.close();
174 return Futures.immediateFuture(Empty.value());
177 protected ListenableFuture<Empty> setupConnection(final NodeId nodeId, final Node configNode) {
178 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
179 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
181 requireNonNull(netconfNode.getHost());
182 requireNonNull(netconfNode.getPort());
184 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
185 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
186 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
187 final NetconfReconnectingClientConfiguration clientConfig =
188 getClientConfig(netconfClientSessionListener, netconfNode, nodeId);
189 final ListenableFuture<Empty> future =
190 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
192 activeConnectors.put(nodeId, deviceCommunicatorDTO);
194 Futures.addCallback(future, new FutureCallback<>() {
196 public void onSuccess(final Empty result) {
197 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
201 public void onFailure(final Throwable throwable) {
202 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
203 // remove this node from active connectors?
205 }, MoreExecutors.directExecutor());
210 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
211 final NetconfNodeAugmentedOptional nodeOptional) {
212 final var deviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
213 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
215 final var deviceSalFacade = new NetconfTopologyDeviceSalFacade(deviceId, mountPointService,
216 node.requireLockDatastore(), dataBroker);
217 // The facade we are going it present to NetconfDevice
218 RemoteDeviceHandler salFacade;
219 final KeepaliveSalFacade keepAliveFacade;
220 if (keepaliveDelay > 0) {
221 LOG.info("Adding keepalive facade, for device {}", nodeId);
222 salFacade = keepAliveFacade = new KeepaliveSalFacade(deviceId, deviceSalFacade,
223 keepaliveExecutor.getExecutor(), keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
225 salFacade = deviceSalFacade;
226 keepAliveFacade = null;
229 // Setup reconnection on empty context, if so configured
230 if (nodeOptional != null && nodeOptional.getIgnoreMissingSchemaSources().getAllowed()) {
231 LOG.warn("Ignoring missing schema sources is not currently implemented for {}", deviceId);
234 final RemoteDevice<NetconfDeviceCommunicator> device;
235 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
236 if (node.requireSchemaless()) {
237 device = new SchemalessNetconfDevice(baseSchemas, deviceId, salFacade);
238 yanglibRegistrations = List.of();
240 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(),
242 device = new NetconfDeviceBuilder()
243 .setReconnectOnSchemasChange(node.requireReconnectOnChangedSchema())
244 .setSchemaResourcesDTO(resources)
245 .setGlobalProcessingExecutor(processingExecutor)
247 .setSalFacade(salFacade)
248 .setDeviceActionFactory(deviceActionFactory)
249 .setBaseSchemas(baseSchemas)
251 yanglibRegistrations = registerDeviceSchemaSources(deviceId, node, resources);
254 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
255 if (rpcMessageLimit < 1) {
256 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", deviceId);
259 final var netconfDeviceCommunicator = new NetconfDeviceCommunicator(deviceId, device, rpcMessageLimit,
260 NetconfNodeUtils.extractUserCapabilities(node));
262 if (keepAliveFacade != null) {
263 keepAliveFacade.setListener(netconfDeviceCommunicator);
266 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
269 private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
270 final NetconfNode node, final SchemaResourcesDTO resources) {
271 final var yangLibrary = node.getYangLibrary();
272 if (yangLibrary != null) {
273 final Uri uri = yangLibrary.getYangLibraryUrl();
275 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
276 final String yangLibURL = uri.getValue();
277 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
279 // pre register yang library sources as fallback schemas to schema registry
280 final LibraryModulesSchemas schemas;
281 final String yangLibUsername = yangLibrary.getUsername();
282 final String yangLigPassword = yangLibrary.getPassword();
283 if (yangLibUsername != null && yangLigPassword != null) {
284 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
286 schemas = LibraryModulesSchemas.create(yangLibURL);
289 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
290 registrations.add(schemaRegistry.registerSchemaSource(new LibrarySchemaSourceProvider(
291 remoteDeviceId, schemas.getAvailableModels()),
292 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
293 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
295 return List.copyOf(registrations);
302 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
303 final NetconfNode node, final NodeId nodeId) {
304 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
305 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
306 node.requireSleepFactor().decimalValue());
307 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
308 final var protocol = node.getProtocol();
309 if (node.requireTcpOnly()) {
310 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
311 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
312 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
313 } else if (protocol == null || protocol.getName() == Name.SSH) {
314 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
315 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
316 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
317 } else if (protocol.getName() == Name.TLS) {
318 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
319 .withSslHandlerFactory(sslHandlerFactoryProvider.getSslHandlerFactory(protocol.getSpecification()))
320 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
322 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
325 if (node.getOdlHelloMessageCapabilities() != null) {
326 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
327 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
330 return reconnectingClientConfigurationBuilder
331 .withName(nodeId.getValue())
332 .withAddress(NetconfNodeUtils.toInetSocketAddress(node))
333 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
334 .withReconnectStrategy(sf.createReconnectStrategy())
335 .withConnectStrategyFactory(sf)
336 .withSessionListener(listener)
340 private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
342 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev230430
343 .credentials.credentials.LoginPassword loginPassword) {
344 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
346 if (credentials instanceof LoginPwUnencrypted unencrypted) {
347 final var loginPassword = unencrypted.getLoginPasswordUnencrypted();
348 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
350 if (credentials instanceof LoginPw loginPw) {
351 final var loginPassword = loginPw.getLoginPassword();
352 return new LoginPasswordHandler(loginPassword.getUsername(),
353 encryptionService.decrypt(loginPassword.getPassword()));
355 if (credentials instanceof KeyAuth keyAuth) {
356 final var keyPair = keyAuth.getKeyBased();
357 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(), credentialProvider,
360 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());