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.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;
20 import java.net.InetSocketAddress;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
26 import java.util.Optional;
27 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
28 import org.opendaylight.controller.config.threadpool.ScheduledThreadPool;
29 import org.opendaylight.controller.config.threadpool.ThreadPool;
30 import org.opendaylight.mdsal.binding.api.DataBroker;
31 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
32 import org.opendaylight.netconf.api.NetconfMessage;
33 import org.opendaylight.netconf.client.NetconfClientDispatcher;
34 import org.opendaylight.netconf.client.NetconfClientSessionListener;
35 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
36 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
37 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
38 import org.opendaylight.netconf.nettyutil.ReconnectStrategyFactory;
39 import org.opendaylight.netconf.nettyutil.TimedReconnectStrategyFactory;
40 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
41 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
42 import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory;
43 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
44 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
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.NetconfDeviceCapabilities;
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.NetconfKeystoreAdapter;
57 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
58 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseNetconfSchemas;
59 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
60 import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
61 import org.opendaylight.netconf.topology.api.NetconfTopology;
62 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
63 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
64 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
65 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
77 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.schema.storage.YangLibrary;
79 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
80 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
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 = 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 = dataBroker;
124 this.mountPointService = mountPointService;
125 this.encryptionService = encryptionService;
126 this.baseSchemas = requireNonNull(baseSchemas);
128 this.keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
132 public ListenableFuture<NetconfDeviceCapabilities> connectNode(final NodeId nodeId, final Node configNode) {
133 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
134 return setupConnection(nodeId, configNode);
138 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
140 * @param nodeConfiguration Node configuration container.
141 * @return String representation of node configuration with credentials replaced by asterisks.
144 public static String hideCredentials(final Node nodeConfiguration) {
145 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
146 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
147 final String nodeConfigurationString = nodeConfiguration.toString();
148 return nodeConfigurationString.replace(nodeCredentials, "***");
152 public ListenableFuture<Void> disconnectNode(final NodeId nodeId) {
153 LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue());
155 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
156 if (connectorDTO == null) {
157 return Futures.immediateFailedFuture(
158 new IllegalStateException("Unable to disconnect device that is not connected"));
161 connectorDTO.close();
162 return Futures.immediateFuture(null);
165 protected ListenableFuture<NetconfDeviceCapabilities> setupConnection(final NodeId nodeId,
166 final Node configNode) {
167 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
168 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
170 requireNonNull(netconfNode.getHost());
171 requireNonNull(netconfNode.getPort());
173 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
174 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
175 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
176 final NetconfReconnectingClientConfiguration clientConfig =
177 getClientConfig(netconfClientSessionListener, netconfNode);
178 final ListenableFuture<NetconfDeviceCapabilities> future =
179 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
181 activeConnectors.put(nodeId, deviceCommunicatorDTO);
183 Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
185 public void onSuccess(final NetconfDeviceCapabilities result) {
186 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
190 public void onFailure(final Throwable throwable) {
191 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
192 // remove this node from active connectors?
194 }, MoreExecutors.directExecutor());
199 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
200 return createDeviceCommunicator(nodeId, node, null);
203 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
204 final NetconfNodeAugmentedOptional nodeOptional) {
205 final Host host = node.getHost();
206 final IpAddress ipAddress = host.getIpAddress();
207 final InetSocketAddress address;
208 if (ipAddress != null) {
209 address = new InetSocketAddress(IetfInetUtil.INSTANCE.inetAddressFor(ipAddress),
210 node.getPort().getValue().toJava());
212 address = new InetSocketAddress(host.getDomainName().getValue(),
213 node.getPort().getValue().toJava());
215 final RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address);
217 final long keepaliveDelay = node.getKeepaliveDelay().toJava();
218 RemoteDeviceHandler<NetconfSessionPreferences> salFacade = createSalFacade(remoteDeviceId);
219 if (keepaliveDelay > 0) {
220 LOG.info("Adding keepalive facade, for device {}", nodeId);
221 salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, this.keepaliveExecutor.getExecutor(),
222 keepaliveDelay, node.getDefaultRequestTimeoutMillis().toJava());
225 final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> device;
226 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
227 if (node.getSchemaless()) {
228 device = new SchemalessNetconfDevice(baseSchemas, remoteDeviceId, salFacade);
229 yanglibRegistrations = List.of();
231 final boolean reconnectOnChangedSchema = node.getReconnectOnChangedSchema();
232 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node, nodeId.getValue());
233 device = new NetconfDeviceBuilder()
234 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
235 .setSchemaResourcesDTO(resources)
236 .setGlobalProcessingExecutor(this.processingExecutor)
237 .setId(remoteDeviceId)
238 .setSalFacade(salFacade)
240 .setEventExecutor(eventExecutor)
241 .setNodeOptional(nodeOptional)
242 .setDeviceActionFactory(deviceActionFactory)
243 .setBaseSchemas(baseSchemas)
245 yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources);
248 final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
249 final int rpcMessageLimit = node.getConcurrentRpcLimit().toJava();
250 if (rpcMessageLimit < 1) {
251 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
254 final NetconfDeviceCommunicator netconfDeviceCommunicator =
255 userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
256 userCapabilities.get(), rpcMessageLimit)
257 : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
259 if (salFacade instanceof KeepaliveSalFacade) {
260 ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
263 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
266 private List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
267 final NetconfNode node, final SchemaResourcesDTO resources) {
268 final YangLibrary 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);
300 * Sets the private key path from location specified in configuration file using blueprint.
302 public void setPrivateKeyPath(final String privateKeyPath) {
303 this.privateKeyPath = privateKeyPath;
307 * Sets the private key passphrase from location specified in configuration file using blueprint.
309 public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
310 this.privateKeyPassphrase = privateKeyPassphrase;
313 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
314 final NetconfNode node) {
315 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
316 node.getMaxConnectionAttempts().toJava(), node.getBetweenAttemptsTimeoutMillis().toJava(),
317 node.getSleepFactor());
318 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
319 final Protocol protocol = node.getProtocol();
320 if (node.getTcpOnly()) {
321 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
322 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
323 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
324 } else if (protocol == null || protocol.getName() == Name.SSH) {
325 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
326 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
327 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
328 } else if (protocol.getName() == Name.TLS) {
329 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
330 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
331 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
333 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
336 if (node.getOdlHelloMessageCapabilities() != null) {
337 reconnectingClientConfigurationBuilder
338 .withOdlHelloCapabilities(node.getOdlHelloMessageCapabilities().getCapability());
341 return reconnectingClientConfigurationBuilder
342 .withAddress(getSocketAddress(node.getHost(), node.getPort().getValue().toJava()))
343 .withConnectionTimeoutMillis(node.getConnectionTimeoutMillis().toJava())
344 .withReconnectStrategy(sf.createReconnectStrategy())
345 .withConnectStrategyFactory(sf)
346 .withSessionListener(listener)
350 private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
351 if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
352 .rev150114.netconf.node.credentials.credentials.LoginPassword) {
353 final org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
354 .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword
355 = (org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
356 .rev150114.netconf.node.credentials.credentials.LoginPassword) credentials;
357 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
359 if (credentials instanceof LoginPwUnencrypted) {
360 final LoginPasswordUnencrypted loginPassword =
361 ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
362 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
364 if (credentials instanceof LoginPw) {
365 final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
366 return new LoginPasswordHandler(loginPassword.getUsername(),
367 encryptionService.decrypt(loginPassword.getPassword()));
369 if (credentials instanceof KeyAuth) {
370 final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
371 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
372 keystoreAdapter, encryptionService);
374 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
377 protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
379 private static InetSocketAddress getSocketAddress(final Host host, final int port) {
380 if (host.getDomainName() != null) {
381 return new InetSocketAddress(host.getDomainName().getValue(), port);
384 final IpAddress ipAddress = host.getIpAddress();
385 final String ip = ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue()
386 : ipAddress.getIpv6Address().getValue();
387 return new InetSocketAddress(ip, port);
390 private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
391 // if none of yang-module-capabilities or non-module-capabilities is specified
392 // just return absent
393 if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
394 return Optional.empty();
397 final List<String> capabilities = new ArrayList<>();
399 boolean overrideYangModuleCaps = false;
400 if (node.getYangModuleCapabilities() != null) {
401 capabilities.addAll(node.getYangModuleCapabilities().getCapability());
402 overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
405 //non-module capabilities should not exist in yang module capabilities
406 final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
407 Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
408 "List yang-module-capabilities/capability should contain only module based capabilities. "
409 + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
411 boolean overrideNonModuleCaps = false;
412 if (node.getNonModuleCapabilities() != null) {
413 capabilities.addAll(node.getNonModuleCapabilities().getCapability());
414 overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
417 return Optional.of(new UserPreferences(NetconfSessionPreferences
418 .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));