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.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.NetconfDeviceCommunicator;
51 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
52 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceSalFacade;
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.RemoteDeviceId;
57 import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
58 import org.opendaylight.netconf.topology.api.NetconfTopology;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.schema.storage.YangLibrary;
72 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
73 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
74 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
75 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
76 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
77 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
78 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
79 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
80 import org.opendaylight.yangtools.yang.common.Empty;
81 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
82 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
83 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
84 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
85 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
86 import org.slf4j.Logger;
87 import org.slf4j.LoggerFactory;
89 public abstract class AbstractNetconfTopology implements NetconfTopology {
90 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
92 private final NetconfClientDispatcher clientDispatcher;
93 private final EventExecutor eventExecutor;
94 private final DeviceActionFactory deviceActionFactory;
95 private final NetconfKeystoreAdapter keystoreAdapter;
96 private final SchemaResourceManager schemaManager;
97 private final BaseNetconfSchemas baseSchemas;
99 protected final ScheduledThreadPool keepaliveExecutor;
100 protected final ListeningExecutorService processingExecutor;
101 protected final DataBroker dataBroker;
102 protected final DOMMountPointService mountPointService;
103 protected final String topologyId;
104 protected String privateKeyPath;
105 protected String privateKeyPassphrase;
106 protected final AAAEncryptionService encryptionService;
107 protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
109 protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
110 final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
111 final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
112 final DataBroker dataBroker, final DOMMountPointService mountPointService,
113 final AAAEncryptionService encryptionService,
114 final DeviceActionFactory deviceActionFactory,
115 final BaseNetconfSchemas baseSchemas) {
116 this.topologyId = requireNonNull(topologyId);
117 this.clientDispatcher = clientDispatcher;
118 this.eventExecutor = eventExecutor;
119 this.keepaliveExecutor = keepaliveExecutor;
120 this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
121 this.schemaManager = requireNonNull(schemaManager);
122 this.deviceActionFactory = deviceActionFactory;
123 this.dataBroker = requireNonNull(dataBroker);
124 this.mountPointService = mountPointService;
125 this.encryptionService = encryptionService;
126 this.baseSchemas = requireNonNull(baseSchemas);
128 keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
130 // FIXME: this should be a put(), as we are initializing and will be re-populating the datastore with all the
131 // devices. Whatever has been there before should be nuked to properly re-align lifecycle.
132 final var wtx = dataBroker.newWriteOnlyTransaction();
133 wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
134 .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
135 .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
136 final var future = wtx.commit();
139 } catch (InterruptedException | ExecutionException e) {
140 LOG.error("Unable to initialize topology {}", topologyId, e);
141 throw new IllegalStateException(e);
144 LOG.debug("Topology {} initialized", topologyId);
148 public ListenableFuture<Empty> connectNode(final NodeId nodeId, final Node configNode) {
149 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
150 return setupConnection(nodeId, configNode);
154 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
156 * @param nodeConfiguration Node configuration container.
157 * @return String representation of node configuration with credentials replaced by asterisks.
160 public static String hideCredentials(final Node nodeConfiguration) {
161 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
162 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
163 final String nodeConfigurationString = nodeConfiguration.toString();
164 return nodeConfigurationString.replace(nodeCredentials, "***");
168 public ListenableFuture<Empty> disconnectNode(final NodeId nodeId) {
169 final var nodeName = nodeId.getValue();
170 LOG.debug("Disconnecting RemoteDevice{{}}", nodeName);
172 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
173 if (connectorDTO == null) {
174 return Futures.immediateFailedFuture(
175 new IllegalStateException("Cannot disconnect " + nodeName + " as it is not connected"));
178 connectorDTO.close();
179 return Futures.immediateFuture(Empty.value());
182 protected ListenableFuture<Empty> setupConnection(final NodeId nodeId, final Node configNode) {
183 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
184 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
186 requireNonNull(netconfNode.getHost());
187 requireNonNull(netconfNode.getPort());
189 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
190 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
191 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
192 final NetconfReconnectingClientConfiguration clientConfig =
193 getClientConfig(netconfClientSessionListener, netconfNode, nodeId);
194 final ListenableFuture<Empty> future =
195 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
197 activeConnectors.put(nodeId, deviceCommunicatorDTO);
199 Futures.addCallback(future, new FutureCallback<>() {
201 public void onSuccess(final Empty result) {
202 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
206 public void onFailure(final Throwable throwable) {
207 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
208 // remove this node from active connectors?
210 }, MoreExecutors.directExecutor());
215 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
216 return createDeviceCommunicator(nodeId, node, null);
219 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
220 final NetconfNodeAugmentedOptional nodeOptional) {
221 final var deviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
222 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
224 final var deviceSalFacade = new NetconfDeviceSalFacade(deviceId, mountPointService, dataBroker, topologyId);
225 // The facade we are going it present to NetconfDevice
226 RemoteDeviceHandler salFacade;
227 final KeepaliveSalFacade keepAliveFacade;
228 if (keepaliveDelay > 0) {
229 LOG.info("Adding keepalive facade, for device {}", nodeId);
230 salFacade = keepAliveFacade = new KeepaliveSalFacade(deviceId, deviceSalFacade,
231 keepaliveExecutor.getExecutor(), keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
233 salFacade = deviceSalFacade;
234 keepAliveFacade = null;
237 final RemoteDevice<NetconfDeviceCommunicator> device;
238 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
239 if (node.requireSchemaless()) {
240 device = new SchemalessNetconfDevice(baseSchemas, deviceId, salFacade);
241 yanglibRegistrations = List.of();
243 final boolean reconnectOnChangedSchema = node.requireReconnectOnChangedSchema();
244 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(),
246 device = new NetconfDeviceBuilder()
247 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
248 .setSchemaResourcesDTO(resources)
249 .setGlobalProcessingExecutor(processingExecutor)
251 .setSalFacade(salFacade)
253 .setEventExecutor(eventExecutor)
254 .setNodeOptional(nodeOptional)
255 .setDeviceActionFactory(deviceActionFactory)
256 .setBaseSchemas(baseSchemas)
258 yanglibRegistrations = registerDeviceSchemaSources(deviceId, node, resources);
261 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
262 if (rpcMessageLimit < 1) {
263 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", deviceId);
266 final var netconfDeviceCommunicator = new NetconfDeviceCommunicator(deviceId, device, rpcMessageLimit,
267 NetconfNodeUtils.extractUserCapabilities(node));
269 if (keepAliveFacade != null) {
270 keepAliveFacade.setListener(netconfDeviceCommunicator);
273 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
276 private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
277 final NetconfNode node, final SchemaResourcesDTO resources) {
278 final YangLibrary yangLibrary = node.getYangLibrary();
279 if (yangLibrary != null) {
280 final Uri uri = yangLibrary.getYangLibraryUrl();
282 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
283 final String yangLibURL = uri.getValue();
284 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
286 // pre register yang library sources as fallback schemas to schema registry
287 final LibraryModulesSchemas schemas;
288 final String yangLibUsername = yangLibrary.getUsername();
289 final String yangLigPassword = yangLibrary.getPassword();
290 if (yangLibUsername != null && yangLigPassword != null) {
291 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
293 schemas = LibraryModulesSchemas.create(yangLibURL);
296 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
297 registrations.add(schemaRegistry.registerSchemaSource(new YangLibrarySchemaYangSourceProvider(
298 remoteDeviceId, schemas.getAvailableModels()),
299 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
300 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
302 return List.copyOf(registrations);
310 * Sets the private key path from location specified in configuration file using blueprint.
312 public void setPrivateKeyPath(final String privateKeyPath) {
313 this.privateKeyPath = privateKeyPath;
317 * Sets the private key passphrase from location specified in configuration file using blueprint.
319 public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
320 this.privateKeyPassphrase = privateKeyPassphrase;
323 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
324 final NetconfNode node, final NodeId nodeId) {
325 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
326 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
327 node.requireSleepFactor().decimalValue());
328 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
329 final Protocol protocol = node.getProtocol();
330 if (node.requireTcpOnly()) {
331 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
332 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
333 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
334 } else if (protocol == null || protocol.getName() == Name.SSH) {
335 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
336 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
337 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
338 } else if (protocol.getName() == Name.TLS) {
339 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
340 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
341 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
343 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
346 if (node.getOdlHelloMessageCapabilities() != null) {
347 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
348 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
351 return reconnectingClientConfigurationBuilder
352 .withName(nodeId.getValue())
353 .withAddress(NetconfNodeUtils.toInetSocketAddress(node))
354 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
355 .withReconnectStrategy(sf.createReconnectStrategy())
356 .withConnectStrategyFactory(sf)
357 .withSessionListener(listener)
361 private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
363 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
364 .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword) {
365 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
367 if (credentials instanceof LoginPwUnencrypted) {
368 final LoginPasswordUnencrypted loginPassword =
369 ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
370 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
372 if (credentials instanceof LoginPw) {
373 final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
374 return new LoginPasswordHandler(loginPassword.getUsername(),
375 encryptionService.decrypt(loginPassword.getPassword()));
377 if (credentials instanceof KeyAuth) {
378 final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
379 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
380 keystoreAdapter, encryptionService);
382 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());