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 java.util.concurrent.ExecutionException;
28 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
29 import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
30 import org.opendaylight.controller.config.threadpool.ThreadPool;
31 import org.opendaylight.mdsal.binding.api.DataBroker;
32 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
33 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
34 import org.opendaylight.netconf.client.NetconfClientDispatcher;
35 import org.opendaylight.netconf.client.NetconfClientSessionListener;
36 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
37 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
38 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
39 import org.opendaylight.netconf.nettyutil.ReconnectStrategyFactory;
40 import org.opendaylight.netconf.nettyutil.TimedReconnectStrategyFactory;
41 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
42 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
43 import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory;
44 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
45 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
46 import org.opendaylight.netconf.sal.connect.api.SchemaResourceManager;
47 import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
48 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice.SchemaResourcesDTO;
49 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
50 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
51 import org.opendaylight.netconf.sal.connect.netconf.auth.DatastoreBackedPublicKeyAuth;
52 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
53 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
54 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
55 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
56 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceSalFacade;
57 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
58 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
59 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseNetconfSchemas;
60 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
61 import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
62 import org.opendaylight.netconf.topology.api.NetconfTopology;
63 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.schema.storage.YangLibrary;
77 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
78 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
79 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
80 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
81 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
82 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
83 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
84 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
85 import org.opendaylight.yangtools.yang.common.Empty;
86 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
87 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
88 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
89 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
90 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
91 import org.slf4j.Logger;
92 import org.slf4j.LoggerFactory;
94 public abstract class AbstractNetconfTopology implements NetconfTopology {
95 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
97 private final NetconfClientDispatcher clientDispatcher;
98 private final EventExecutor eventExecutor;
99 private final DeviceActionFactory deviceActionFactory;
100 private final NetconfKeystoreAdapter keystoreAdapter;
101 private final SchemaResourceManager schemaManager;
102 private final BaseNetconfSchemas baseSchemas;
104 protected final ScheduledThreadPool keepaliveExecutor;
105 protected final ListeningExecutorService processingExecutor;
106 protected final DataBroker dataBroker;
107 protected final DOMMountPointService mountPointService;
108 protected final String topologyId;
109 protected String privateKeyPath;
110 protected String privateKeyPassphrase;
111 protected final AAAEncryptionService encryptionService;
112 protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
114 protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
115 final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
116 final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
117 final DataBroker dataBroker, final DOMMountPointService mountPointService,
118 final AAAEncryptionService encryptionService,
119 final DeviceActionFactory deviceActionFactory,
120 final BaseNetconfSchemas baseSchemas) {
121 this.topologyId = requireNonNull(topologyId);
122 this.clientDispatcher = clientDispatcher;
123 this.eventExecutor = eventExecutor;
124 this.keepaliveExecutor = keepaliveExecutor;
125 this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
126 this.schemaManager = requireNonNull(schemaManager);
127 this.deviceActionFactory = deviceActionFactory;
128 this.dataBroker = requireNonNull(dataBroker);
129 this.mountPointService = mountPointService;
130 this.encryptionService = encryptionService;
131 this.baseSchemas = requireNonNull(baseSchemas);
133 keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
135 // FIXME: this should be a put(), as we are initializing and will be re-populating the datastore with all the
136 // devices. Whatever has been there before should be nuked to properly re-align lifecycle.
137 final var wtx = dataBroker.newWriteOnlyTransaction();
138 wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
139 .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
140 .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
141 final var future = wtx.commit();
144 } catch (InterruptedException | ExecutionException e) {
145 LOG.error("Unable to initialize topology {}", topologyId, e);
146 throw new IllegalStateException(e);
149 LOG.debug("Topology {} initialized", topologyId);
153 public ListenableFuture<Empty> connectNode(final NodeId nodeId, final Node configNode) {
154 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
155 return setupConnection(nodeId, configNode);
159 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
161 * @param nodeConfiguration Node configuration container.
162 * @return String representation of node configuration with credentials replaced by asterisks.
165 public static String hideCredentials(final Node nodeConfiguration) {
166 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
167 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
168 final String nodeConfigurationString = nodeConfiguration.toString();
169 return nodeConfigurationString.replace(nodeCredentials, "***");
173 public ListenableFuture<Empty> disconnectNode(final NodeId nodeId) {
174 final var nodeName = nodeId.getValue();
175 LOG.debug("Disconnecting RemoteDevice{{}}", nodeName);
177 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
178 if (connectorDTO == null) {
179 return Futures.immediateFailedFuture(
180 new IllegalStateException("Cannot disconnect " + nodeName + " as it is not connected"));
183 connectorDTO.close();
184 return Futures.immediateFuture(Empty.value());
187 protected ListenableFuture<Empty> setupConnection(final NodeId nodeId, final Node configNode) {
188 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
189 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
191 requireNonNull(netconfNode.getHost());
192 requireNonNull(netconfNode.getPort());
194 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
195 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
196 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
197 final NetconfReconnectingClientConfiguration clientConfig =
198 getClientConfig(netconfClientSessionListener, netconfNode, nodeId);
199 final ListenableFuture<Empty> future =
200 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
202 activeConnectors.put(nodeId, deviceCommunicatorDTO);
204 Futures.addCallback(future, new FutureCallback<>() {
206 public void onSuccess(final Empty result) {
207 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
211 public void onFailure(final Throwable throwable) {
212 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
213 // remove this node from active connectors?
215 }, MoreExecutors.directExecutor());
220 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
221 return createDeviceCommunicator(nodeId, node, null);
224 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
225 final NetconfNodeAugmentedOptional nodeOptional) {
226 final var deviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
227 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
229 final var deviceSalFacade = new NetconfDeviceSalFacade(deviceId, mountPointService, dataBroker, topologyId);
230 // The facade we are going it present to NetconfDevice
231 RemoteDeviceHandler salFacade;
232 final KeepaliveSalFacade keepAliveFacade;
233 if (keepaliveDelay > 0) {
234 LOG.info("Adding keepalive facade, for device {}", nodeId);
235 salFacade = keepAliveFacade = new KeepaliveSalFacade(deviceId, deviceSalFacade,
236 keepaliveExecutor.getExecutor(), keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
238 salFacade = deviceSalFacade;
239 keepAliveFacade = null;
242 final RemoteDevice<NetconfDeviceCommunicator> device;
243 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
244 if (node.requireSchemaless()) {
245 device = new SchemalessNetconfDevice(baseSchemas, deviceId, salFacade);
246 yanglibRegistrations = List.of();
248 final boolean reconnectOnChangedSchema = node.requireReconnectOnChangedSchema();
249 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(),
251 device = new NetconfDeviceBuilder()
252 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
253 .setSchemaResourcesDTO(resources)
254 .setGlobalProcessingExecutor(processingExecutor)
256 .setSalFacade(salFacade)
258 .setEventExecutor(eventExecutor)
259 .setNodeOptional(nodeOptional)
260 .setDeviceActionFactory(deviceActionFactory)
261 .setBaseSchemas(baseSchemas)
263 yanglibRegistrations = registerDeviceSchemaSources(deviceId, node, resources);
266 final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
267 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
268 if (rpcMessageLimit < 1) {
269 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", deviceId);
272 final NetconfDeviceCommunicator netconfDeviceCommunicator =
273 userCapabilities.isPresent() ? new NetconfDeviceCommunicator(deviceId, device,
274 userCapabilities.get(), rpcMessageLimit)
275 : new NetconfDeviceCommunicator(deviceId, device, rpcMessageLimit);
277 if (keepAliveFacade != null) {
278 keepAliveFacade.setListener(netconfDeviceCommunicator);
281 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
284 private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
285 final NetconfNode node, final SchemaResourcesDTO resources) {
286 final YangLibrary yangLibrary = node.getYangLibrary();
287 if (yangLibrary != null) {
288 final Uri uri = yangLibrary.getYangLibraryUrl();
290 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
291 final String yangLibURL = uri.getValue();
292 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
294 // pre register yang library sources as fallback schemas to schema registry
295 final LibraryModulesSchemas schemas;
296 final String yangLibUsername = yangLibrary.getUsername();
297 final String yangLigPassword = yangLibrary.getPassword();
298 if (yangLibUsername != null && yangLigPassword != null) {
299 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
301 schemas = LibraryModulesSchemas.create(yangLibURL);
304 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
305 registrations.add(schemaRegistry.registerSchemaSource(new YangLibrarySchemaYangSourceProvider(
306 remoteDeviceId, schemas.getAvailableModels()),
307 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
308 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
310 return List.copyOf(registrations);
318 * Sets the private key path from location specified in configuration file using blueprint.
320 public void setPrivateKeyPath(final String privateKeyPath) {
321 this.privateKeyPath = privateKeyPath;
325 * Sets the private key passphrase from location specified in configuration file using blueprint.
327 public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
328 this.privateKeyPassphrase = privateKeyPassphrase;
331 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
332 final NetconfNode node, final NodeId nodeId) {
333 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
334 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
335 node.requireSleepFactor().decimalValue());
336 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
337 final Protocol protocol = node.getProtocol();
338 if (node.requireTcpOnly()) {
339 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
340 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
341 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
342 } else if (protocol == null || protocol.getName() == Name.SSH) {
343 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
344 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
345 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
346 } else if (protocol.getName() == Name.TLS) {
347 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
348 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
349 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
351 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
354 if (node.getOdlHelloMessageCapabilities() != null) {
355 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
356 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
359 return reconnectingClientConfigurationBuilder
360 .withName(nodeId.getValue())
361 .withAddress(NetconfNodeUtils.toInetSocketAddress(node))
362 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
363 .withReconnectStrategy(sf.createReconnectStrategy())
364 .withConnectStrategyFactory(sf)
365 .withSessionListener(listener)
369 private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
371 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
372 .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword) {
373 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
375 if (credentials instanceof LoginPwUnencrypted) {
376 final LoginPasswordUnencrypted loginPassword =
377 ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
378 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
380 if (credentials instanceof LoginPw) {
381 final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
382 return new LoginPasswordHandler(loginPassword.getUsername(),
383 encryptionService.decrypt(loginPassword.getPassword()));
385 if (credentials instanceof KeyAuth) {
386 final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
387 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
388 keystoreAdapter, encryptionService);
390 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
393 private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
394 // if none of yang-module-capabilities or non-module-capabilities is specified
395 // just return absent
396 if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
397 return Optional.empty();
400 final List<String> capabilities = new ArrayList<>();
402 boolean overrideYangModuleCaps = false;
403 if (node.getYangModuleCapabilities() != null) {
404 capabilities.addAll(node.getYangModuleCapabilities().getCapability());
405 overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
408 //non-module capabilities should not exist in yang module capabilities
409 final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
410 Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
411 "List yang-module-capabilities/capability should contain only module based capabilities. "
412 + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
414 boolean overrideNonModuleCaps = false;
415 if (node.getNonModuleCapabilities() != null) {
416 capabilities.addAll(node.getNonModuleCapabilities().getCapability());
417 overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
420 return Optional.of(new UserPreferences(NetconfSessionPreferences
421 .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));