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.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.RemoteDeviceId;
45 import org.opendaylight.netconf.sal.connect.api.SchemaResourceManager;
46 import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
47 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice.SchemaResourcesDTO;
48 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
49 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
50 import org.opendaylight.netconf.sal.connect.netconf.auth.DatastoreBackedPublicKeyAuth;
51 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
52 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
53 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
54 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
55 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseNetconfSchemas;
56 import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
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.rev221225.connection.parameters.Protocol.Name;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev221225.credentials.Credentials;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev221225.credentials.credentials.KeyAuth;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev221225.credentials.credentials.LoginPw;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev221225.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 NetconfKeystoreAdapter keystoreAdapter;
90 private final SchemaResourceManager schemaManager;
91 private final BaseNetconfSchemas baseSchemas;
93 protected final ScheduledThreadPool keepaliveExecutor;
94 protected final ListeningExecutorService processingExecutor;
95 protected final DataBroker dataBroker;
96 protected final DOMMountPointService mountPointService;
97 protected final String topologyId;
98 protected final AAAEncryptionService encryptionService;
99 protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
101 protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
102 final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
103 final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
104 final DataBroker dataBroker, final DOMMountPointService mountPointService,
105 final AAAEncryptionService encryptionService,
106 final DeviceActionFactory deviceActionFactory,
107 final BaseNetconfSchemas baseSchemas) {
108 this.topologyId = requireNonNull(topologyId);
109 this.clientDispatcher = clientDispatcher;
110 this.eventExecutor = eventExecutor;
111 this.keepaliveExecutor = keepaliveExecutor;
112 this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
113 this.schemaManager = requireNonNull(schemaManager);
114 this.deviceActionFactory = deviceActionFactory;
115 this.dataBroker = requireNonNull(dataBroker);
116 this.mountPointService = mountPointService;
117 this.encryptionService = encryptionService;
118 this.baseSchemas = requireNonNull(baseSchemas);
120 keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
122 // FIXME: this should be a put(), as we are initializing and will be re-populating the datastore with all the
123 // devices. Whatever has been there before should be nuked to properly re-align lifecycle.
124 final var wtx = dataBroker.newWriteOnlyTransaction();
125 wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
126 .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
127 .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
128 final var future = wtx.commit();
131 } catch (InterruptedException | ExecutionException e) {
132 LOG.error("Unable to initialize topology {}", topologyId, e);
133 throw new IllegalStateException(e);
136 LOG.debug("Topology {} initialized", topologyId);
140 public ListenableFuture<Empty> connectNode(final NodeId nodeId, final Node configNode) {
141 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
142 return setupConnection(nodeId, configNode);
146 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
148 * @param nodeConfiguration Node configuration container.
149 * @return String representation of node configuration with credentials replaced by asterisks.
152 public static String hideCredentials(final Node nodeConfiguration) {
153 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
154 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
155 final String nodeConfigurationString = nodeConfiguration.toString();
156 return nodeConfigurationString.replace(nodeCredentials, "***");
160 public ListenableFuture<Empty> disconnectNode(final NodeId nodeId) {
161 final var nodeName = nodeId.getValue();
162 LOG.debug("Disconnecting RemoteDevice{{}}", nodeName);
164 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
165 if (connectorDTO == null) {
166 return Futures.immediateFailedFuture(
167 new IllegalStateException("Cannot disconnect " + nodeName + " as it is not connected"));
170 connectorDTO.close();
171 return Futures.immediateFuture(Empty.value());
174 protected ListenableFuture<Empty> setupConnection(final NodeId nodeId, final Node configNode) {
175 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
176 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
178 requireNonNull(netconfNode.getHost());
179 requireNonNull(netconfNode.getPort());
181 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
182 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
183 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
184 final NetconfReconnectingClientConfiguration clientConfig =
185 getClientConfig(netconfClientSessionListener, netconfNode, nodeId);
186 final ListenableFuture<Empty> future =
187 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
189 activeConnectors.put(nodeId, deviceCommunicatorDTO);
191 Futures.addCallback(future, new FutureCallback<>() {
193 public void onSuccess(final Empty result) {
194 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
198 public void onFailure(final Throwable throwable) {
199 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
200 // remove this node from active connectors?
202 }, MoreExecutors.directExecutor());
207 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
208 final NetconfNodeAugmentedOptional nodeOptional) {
209 final var deviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
210 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
212 final var deviceSalFacade = new NetconfTopologyDeviceSalFacade(deviceId, mountPointService,
213 node.requireLockDatastore(), dataBroker);
214 // The facade we are going it present to NetconfDevice
215 RemoteDeviceHandler salFacade;
216 final KeepaliveSalFacade keepAliveFacade;
217 if (keepaliveDelay > 0) {
218 LOG.info("Adding keepalive facade, for device {}", nodeId);
219 salFacade = keepAliveFacade = new KeepaliveSalFacade(deviceId, deviceSalFacade,
220 keepaliveExecutor.getExecutor(), keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
222 salFacade = deviceSalFacade;
223 keepAliveFacade = null;
226 // Setup reconnection on empty context, if so configured
227 if (nodeOptional != null && nodeOptional.getIgnoreMissingSchemaSources().getAllowed()) {
228 LOG.warn("Ignoring missing schema sources is not currently implemented for {}", deviceId);
231 final RemoteDevice<NetconfDeviceCommunicator> device;
232 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
233 if (node.requireSchemaless()) {
234 device = new SchemalessNetconfDevice(baseSchemas, deviceId, salFacade);
235 yanglibRegistrations = List.of();
237 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(),
239 device = new NetconfDeviceBuilder()
240 .setReconnectOnSchemasChange(node.requireReconnectOnChangedSchema())
241 .setSchemaResourcesDTO(resources)
242 .setGlobalProcessingExecutor(processingExecutor)
244 .setSalFacade(salFacade)
245 .setDeviceActionFactory(deviceActionFactory)
246 .setBaseSchemas(baseSchemas)
248 yanglibRegistrations = registerDeviceSchemaSources(deviceId, node, resources);
251 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
252 if (rpcMessageLimit < 1) {
253 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", deviceId);
256 final var netconfDeviceCommunicator = new NetconfDeviceCommunicator(deviceId, device, rpcMessageLimit,
257 NetconfNodeUtils.extractUserCapabilities(node));
259 if (keepAliveFacade != null) {
260 keepAliveFacade.setListener(netconfDeviceCommunicator);
263 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
266 private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
267 final NetconfNode node, final SchemaResourcesDTO resources) {
268 final var yangLibrary = node.getYangLibrary();
269 if (yangLibrary != null) {
270 final Uri uri = yangLibrary.getYangLibraryUrl();
272 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
273 final String yangLibURL = uri.getValue();
274 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
276 // pre register yang library sources as fallback schemas to schema registry
277 final LibraryModulesSchemas schemas;
278 final String yangLibUsername = yangLibrary.getUsername();
279 final String yangLigPassword = yangLibrary.getPassword();
280 if (yangLibUsername != null && yangLigPassword != null) {
281 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
283 schemas = LibraryModulesSchemas.create(yangLibURL);
286 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
287 registrations.add(schemaRegistry.registerSchemaSource(new YangLibrarySchemaYangSourceProvider(
288 remoteDeviceId, schemas.getAvailableModels()),
289 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
290 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
292 return List.copyOf(registrations);
299 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
300 final NetconfNode node, final NodeId nodeId) {
301 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
302 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
303 node.requireSleepFactor().decimalValue());
304 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
305 final var protocol = node.getProtocol();
306 if (node.requireTcpOnly()) {
307 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
308 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
309 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
310 } else if (protocol == null || protocol.getName() == Name.SSH) {
311 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
312 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
313 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
314 } else if (protocol.getName() == Name.TLS) {
315 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
316 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
317 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
319 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
322 if (node.getOdlHelloMessageCapabilities() != null) {
323 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
324 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
327 return reconnectingClientConfigurationBuilder
328 .withName(nodeId.getValue())
329 .withAddress(NetconfNodeUtils.toInetSocketAddress(node))
330 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
331 .withReconnectStrategy(sf.createReconnectStrategy())
332 .withConnectStrategyFactory(sf)
333 .withSessionListener(listener)
337 private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
339 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev221225
340 .credentials.credentials.LoginPassword loginPassword) {
341 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
343 if (credentials instanceof LoginPwUnencrypted unencrypted) {
344 final var loginPassword = unencrypted.getLoginPasswordUnencrypted();
345 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
347 if (credentials instanceof LoginPw loginPw) {
348 final var loginPassword = loginPw.getLoginPassword();
349 return new LoginPasswordHandler(loginPassword.getUsername(),
350 encryptionService.decrypt(loginPassword.getPassword()));
352 if (credentials instanceof KeyAuth keyAuth) {
353 final var keyPair = keyAuth.getKeyBased();
354 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
355 keystoreAdapter, encryptionService);
357 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());