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.client.mdsal.spi.KeepaliveSalFacade;
53 import org.opendaylight.netconf.nettyutil.ReconnectStrategyFactory;
54 import org.opendaylight.netconf.nettyutil.TimedReconnectStrategyFactory;
55 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
56 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
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 = createSalFacade(deviceId, node.requireLockDatastore());
216 // The facade we are going it present to NetconfDevice
217 RemoteDeviceHandler salFacade;
218 final KeepaliveSalFacade keepAliveFacade;
219 if (keepaliveDelay > 0) {
220 LOG.info("Adding keepalive facade, for device {}", nodeId);
221 salFacade = keepAliveFacade = new KeepaliveSalFacade(deviceId, deviceSalFacade,
222 keepaliveExecutor.getExecutor(), keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
224 salFacade = deviceSalFacade;
225 keepAliveFacade = null;
228 // Setup reconnection on empty context, if so configured
229 if (nodeOptional != null && nodeOptional.getIgnoreMissingSchemaSources().getAllowed()) {
230 LOG.warn("Ignoring missing schema sources is not currently implemented for {}", deviceId);
233 final RemoteDevice<NetconfDeviceCommunicator> device;
234 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
235 if (node.requireSchemaless()) {
236 device = new SchemalessNetconfDevice(baseSchemas, deviceId, salFacade);
237 yanglibRegistrations = List.of();
239 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(),
241 device = new NetconfDeviceBuilder()
242 .setReconnectOnSchemasChange(node.requireReconnectOnChangedSchema())
243 .setSchemaResourcesDTO(resources)
244 .setGlobalProcessingExecutor(processingExecutor)
246 .setSalFacade(salFacade)
247 .setDeviceActionFactory(deviceActionFactory)
248 .setBaseSchemas(baseSchemas)
250 yanglibRegistrations = registerDeviceSchemaSources(deviceId, node, resources);
253 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
254 if (rpcMessageLimit < 1) {
255 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", deviceId);
258 final var netconfDeviceCommunicator = new NetconfDeviceCommunicator(deviceId, device, rpcMessageLimit,
259 NetconfNodeUtils.extractUserCapabilities(node));
261 if (keepAliveFacade != null) {
262 keepAliveFacade.setListener(netconfDeviceCommunicator);
265 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
268 protected RemoteDeviceHandler createSalFacade(RemoteDeviceId deviceId, boolean lockDatastore) {
269 return new NetconfTopologyDeviceSalFacade(deviceId, mountPointService, lockDatastore, dataBroker);
272 private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
273 final NetconfNode node, final SchemaResourcesDTO resources) {
274 final var yangLibrary = node.getYangLibrary();
275 if (yangLibrary != null) {
276 final Uri uri = yangLibrary.getYangLibraryUrl();
278 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
279 final String yangLibURL = uri.getValue();
280 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
282 // pre register yang library sources as fallback schemas to schema registry
283 final LibraryModulesSchemas schemas;
284 final String yangLibUsername = yangLibrary.getUsername();
285 final String yangLigPassword = yangLibrary.getPassword();
286 if (yangLibUsername != null && yangLigPassword != null) {
287 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
289 schemas = LibraryModulesSchemas.create(yangLibURL);
292 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
293 registrations.add(schemaRegistry.registerSchemaSource(new LibrarySchemaSourceProvider(
294 remoteDeviceId, schemas.getAvailableModels()),
295 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
296 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
298 return List.copyOf(registrations);
305 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
306 final NetconfNode node, final NodeId nodeId) {
307 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
308 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
309 node.requireSleepFactor().decimalValue());
310 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
311 final var protocol = node.getProtocol();
312 if (node.requireTcpOnly()) {
313 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
314 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
315 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
316 } else if (protocol == null || protocol.getName() == Name.SSH) {
317 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
318 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
319 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
320 } else if (protocol.getName() == Name.TLS) {
321 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
322 .withSslHandlerFactory(sslHandlerFactoryProvider.getSslHandlerFactory(protocol.getSpecification()))
323 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
325 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
328 if (node.getOdlHelloMessageCapabilities() != null) {
329 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
330 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
333 return reconnectingClientConfigurationBuilder
334 .withName(nodeId.getValue())
335 .withAddress(NetconfNodeUtils.toInetSocketAddress(node))
336 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
337 .withReconnectStrategy(sf.createReconnectStrategy())
338 .withConnectStrategyFactory(sf)
339 .withSessionListener(listener)
343 private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
345 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev230430
346 .credentials.credentials.LoginPassword loginPassword) {
347 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
349 if (credentials instanceof LoginPwUnencrypted unencrypted) {
350 final var loginPassword = unencrypted.getLoginPasswordUnencrypted();
351 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
353 if (credentials instanceof LoginPw loginPw) {
354 final var loginPassword = loginPw.getLoginPassword();
355 return new LoginPasswordHandler(loginPassword.getUsername(),
356 encryptionService.decrypt(loginPassword.getPassword()));
358 if (credentials instanceof KeyAuth keyAuth) {
359 final var keyPair = keyAuth.getKeyBased();
360 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(), credentialProvider,
363 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());