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;
21 import java.net.InetSocketAddress;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
27 import java.util.Optional;
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.dom.api.DOMMountPointService;
33 import org.opendaylight.netconf.api.NetconfMessage;
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.NetconfDeviceCapabilities;
53 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
54 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
55 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
56 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
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.Host;
64 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
65 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
66 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
77 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
79 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.schema.storage.YangLibrary;
80 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
81 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
82 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
83 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
84 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
85 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
86 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
87 import org.slf4j.Logger;
88 import org.slf4j.LoggerFactory;
90 public abstract class AbstractNetconfTopology implements NetconfTopology {
91 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
93 private final NetconfClientDispatcher clientDispatcher;
94 private final EventExecutor eventExecutor;
95 private final DeviceActionFactory deviceActionFactory;
96 private final NetconfKeystoreAdapter keystoreAdapter;
97 private final SchemaResourceManager schemaManager;
98 private final BaseNetconfSchemas baseSchemas;
100 protected final ScheduledThreadPool keepaliveExecutor;
101 protected final ListeningExecutorService processingExecutor;
102 protected final DataBroker dataBroker;
103 protected final DOMMountPointService mountPointService;
104 protected final String topologyId;
105 protected String privateKeyPath;
106 protected String privateKeyPassphrase;
107 protected final AAAEncryptionService encryptionService;
108 protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
110 protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
111 final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
112 final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
113 final DataBroker dataBroker, final DOMMountPointService mountPointService,
114 final AAAEncryptionService encryptionService,
115 final DeviceActionFactory deviceActionFactory,
116 final BaseNetconfSchemas baseSchemas) {
117 this.topologyId = topologyId;
118 this.clientDispatcher = clientDispatcher;
119 this.eventExecutor = eventExecutor;
120 this.keepaliveExecutor = keepaliveExecutor;
121 this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
122 this.schemaManager = requireNonNull(schemaManager);
123 this.deviceActionFactory = deviceActionFactory;
124 this.dataBroker = dataBroker;
125 this.mountPointService = mountPointService;
126 this.encryptionService = encryptionService;
127 this.baseSchemas = requireNonNull(baseSchemas);
129 this.keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
133 public ListenableFuture<NetconfDeviceCapabilities> connectNode(final NodeId nodeId, final Node configNode) {
134 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
135 return setupConnection(nodeId, configNode);
139 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
141 * @param nodeConfiguration Node configuration container.
142 * @return String representation of node configuration with credentials replaced by asterisks.
145 public static String hideCredentials(final Node nodeConfiguration) {
146 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
147 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
148 final String nodeConfigurationString = nodeConfiguration.toString();
149 return nodeConfigurationString.replace(nodeCredentials, "***");
153 public ListenableFuture<Void> disconnectNode(final NodeId nodeId) {
154 LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue());
156 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
157 if (connectorDTO == null) {
158 return Futures.immediateFailedFuture(
159 new IllegalStateException("Unable to disconnect device that is not connected"));
162 connectorDTO.close();
163 return Futures.immediateFuture(null);
166 protected ListenableFuture<NetconfDeviceCapabilities> setupConnection(final NodeId nodeId,
167 final Node configNode) {
168 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
169 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
171 requireNonNull(netconfNode.getHost());
172 requireNonNull(netconfNode.getPort());
174 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
175 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
176 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
177 final NetconfReconnectingClientConfiguration clientConfig =
178 getClientConfig(netconfClientSessionListener, netconfNode);
179 final ListenableFuture<NetconfDeviceCapabilities> future =
180 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
182 activeConnectors.put(nodeId, deviceCommunicatorDTO);
184 Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
186 public void onSuccess(final NetconfDeviceCapabilities result) {
187 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
191 public void onFailure(final Throwable throwable) {
192 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
193 // remove this node from active connectors?
195 }, MoreExecutors.directExecutor());
200 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
201 return createDeviceCommunicator(nodeId, node, null);
204 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
205 final NetconfNodeAugmentedOptional nodeOptional) {
206 final Host host = node.getHost();
207 final IpAddress ipAddress = host.getIpAddress();
208 final InetSocketAddress address;
209 if (ipAddress != null) {
210 address = new InetSocketAddress(IetfInetUtil.INSTANCE.inetAddressFor(ipAddress),
211 node.getPort().getValue().toJava());
213 address = new InetSocketAddress(host.getDomainName().getValue(),
214 node.getPort().getValue().toJava());
216 final RemoteDeviceId remoteDeviceId = new RemoteDeviceId(nodeId.getValue(), address);
218 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
219 RemoteDeviceHandler<NetconfSessionPreferences> salFacade = createSalFacade(remoteDeviceId);
220 if (keepaliveDelay > 0) {
221 LOG.info("Adding keepalive facade, for device {}", nodeId);
222 salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, this.keepaliveExecutor.getExecutor(),
223 keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
226 final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> device;
227 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
228 if (node.requireSchemaless()) {
229 device = new SchemalessNetconfDevice(baseSchemas, remoteDeviceId, salFacade);
230 yanglibRegistrations = List.of();
232 final boolean reconnectOnChangedSchema = node.requireReconnectOnChangedSchema();
233 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node, nodeId.getValue());
234 device = new NetconfDeviceBuilder()
235 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
236 .setSchemaResourcesDTO(resources)
237 .setGlobalProcessingExecutor(this.processingExecutor)
238 .setId(remoteDeviceId)
239 .setSalFacade(salFacade)
241 .setEventExecutor(eventExecutor)
242 .setNodeOptional(nodeOptional)
243 .setDeviceActionFactory(deviceActionFactory)
244 .setBaseSchemas(baseSchemas)
246 yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources);
249 final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
250 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
251 if (rpcMessageLimit < 1) {
252 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
255 final NetconfDeviceCommunicator netconfDeviceCommunicator =
256 userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
257 userCapabilities.get(), rpcMessageLimit)
258 : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
260 if (salFacade instanceof KeepaliveSalFacade) {
261 ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
264 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
267 private List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
268 final NetconfNode node, final SchemaResourcesDTO resources) {
269 final YangLibrary yangLibrary = node.getYangLibrary();
270 if (yangLibrary != null) {
271 final Uri uri = yangLibrary.getYangLibraryUrl();
273 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
274 final String yangLibURL = uri.getValue();
275 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
277 // pre register yang library sources as fallback schemas to schema registry
278 final LibraryModulesSchemas schemas;
279 final String yangLibUsername = yangLibrary.getUsername();
280 final String yangLigPassword = yangLibrary.getPassword();
281 if (yangLibUsername != null && yangLigPassword != null) {
282 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
284 schemas = LibraryModulesSchemas.create(yangLibURL);
287 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
288 registrations.add(schemaRegistry.registerSchemaSource(new YangLibrarySchemaYangSourceProvider(
289 remoteDeviceId, schemas.getAvailableModels()),
290 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
291 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
293 return List.copyOf(registrations);
301 * Sets the private key path from location specified in configuration file using blueprint.
303 public void setPrivateKeyPath(final String privateKeyPath) {
304 this.privateKeyPath = privateKeyPath;
308 * Sets the private key passphrase from location specified in configuration file using blueprint.
310 public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
311 this.privateKeyPassphrase = privateKeyPassphrase;
314 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
315 final NetconfNode node) {
316 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
317 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
318 node.requireSleepFactor().decimalValue());
319 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
320 final Protocol protocol = node.getProtocol();
321 if (node.requireTcpOnly()) {
322 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
323 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
324 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
325 } else if (protocol == null || protocol.getName() == Name.SSH) {
326 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
327 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
328 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
329 } else if (protocol.getName() == Name.TLS) {
330 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
331 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
332 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
334 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
337 if (node.getOdlHelloMessageCapabilities() != null) {
338 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
339 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
342 return reconnectingClientConfigurationBuilder
343 .withAddress(getSocketAddress(node.getHost(), node.getPort().getValue().toJava()))
344 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
345 .withReconnectStrategy(sf.createReconnectStrategy())
346 .withConnectStrategyFactory(sf)
347 .withSessionListener(listener)
351 private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
352 if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
353 .rev150114.netconf.node.credentials.credentials.LoginPassword) {
354 final org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
355 .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword
356 = (org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
357 .rev150114.netconf.node.credentials.credentials.LoginPassword) credentials;
358 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
360 if (credentials instanceof LoginPwUnencrypted) {
361 final LoginPasswordUnencrypted loginPassword =
362 ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
363 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
365 if (credentials instanceof LoginPw) {
366 final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
367 return new LoginPasswordHandler(loginPassword.getUsername(),
368 encryptionService.decrypt(loginPassword.getPassword()));
370 if (credentials instanceof KeyAuth) {
371 final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
372 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
373 keystoreAdapter, encryptionService);
375 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
378 protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
380 private static InetSocketAddress getSocketAddress(final Host host, final int port) {
381 if (host.getDomainName() != null) {
382 return new InetSocketAddress(host.getDomainName().getValue(), port);
385 final IpAddress ipAddress = host.getIpAddress();
386 final String ip = ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue()
387 : ipAddress.getIpv6Address().getValue();
388 return new InetSocketAddress(ip, port);
391 private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
392 // if none of yang-module-capabilities or non-module-capabilities is specified
393 // just return absent
394 if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
395 return Optional.empty();
398 final List<String> capabilities = new ArrayList<>();
400 boolean overrideYangModuleCaps = false;
401 if (node.getYangModuleCapabilities() != null) {
402 capabilities.addAll(node.getYangModuleCapabilities().getCapability());
403 overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
406 //non-module capabilities should not exist in yang module capabilities
407 final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
408 Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
409 "List yang-module-capabilities/capability should contain only module based capabilities. "
410 + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
412 boolean overrideNonModuleCaps = false;
413 if (node.getNonModuleCapabilities() != null) {
414 capabilities.addAll(node.getNonModuleCapabilities().getCapability());
415 overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
418 return Optional.of(new UserPreferences(NetconfSessionPreferences
419 .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));