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 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.Uri;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.schema.storage.YangLibrary;
76 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
77 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
78 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
79 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
80 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
81 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
82 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
83 import org.slf4j.Logger;
84 import org.slf4j.LoggerFactory;
86 public abstract class AbstractNetconfTopology implements NetconfTopology {
87 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
89 private final NetconfClientDispatcher clientDispatcher;
90 private final EventExecutor eventExecutor;
91 private final DeviceActionFactory deviceActionFactory;
92 private final NetconfKeystoreAdapter keystoreAdapter;
93 private final SchemaResourceManager schemaManager;
94 private final BaseNetconfSchemas baseSchemas;
96 protected final ScheduledThreadPool keepaliveExecutor;
97 protected final ListeningExecutorService processingExecutor;
98 protected final DataBroker dataBroker;
99 protected final DOMMountPointService mountPointService;
100 protected final String topologyId;
101 protected String privateKeyPath;
102 protected String privateKeyPassphrase;
103 protected final AAAEncryptionService encryptionService;
104 protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
106 protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
107 final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
108 final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
109 final DataBroker dataBroker, final DOMMountPointService mountPointService,
110 final AAAEncryptionService encryptionService,
111 final DeviceActionFactory deviceActionFactory,
112 final BaseNetconfSchemas baseSchemas) {
113 this.topologyId = topologyId;
114 this.clientDispatcher = clientDispatcher;
115 this.eventExecutor = eventExecutor;
116 this.keepaliveExecutor = keepaliveExecutor;
117 this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
118 this.schemaManager = requireNonNull(schemaManager);
119 this.deviceActionFactory = deviceActionFactory;
120 this.dataBroker = dataBroker;
121 this.mountPointService = mountPointService;
122 this.encryptionService = encryptionService;
123 this.baseSchemas = requireNonNull(baseSchemas);
125 this.keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
129 public ListenableFuture<NetconfDeviceCapabilities> connectNode(final NodeId nodeId, final Node configNode) {
130 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
131 return setupConnection(nodeId, configNode);
135 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
137 * @param nodeConfiguration Node configuration container.
138 * @return String representation of node configuration with credentials replaced by asterisks.
141 public static String hideCredentials(final Node nodeConfiguration) {
142 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
143 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
144 final String nodeConfigurationString = nodeConfiguration.toString();
145 return nodeConfigurationString.replace(nodeCredentials, "***");
149 public ListenableFuture<Void> disconnectNode(final NodeId nodeId) {
150 LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue());
152 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
153 if (connectorDTO == null) {
154 return Futures.immediateFailedFuture(
155 new IllegalStateException("Unable to disconnect device that is not connected"));
158 connectorDTO.close();
159 return Futures.immediateFuture(null);
162 protected ListenableFuture<NetconfDeviceCapabilities> setupConnection(final NodeId nodeId,
163 final Node configNode) {
164 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
165 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
167 requireNonNull(netconfNode.getHost());
168 requireNonNull(netconfNode.getPort());
170 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
171 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
172 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
173 final NetconfReconnectingClientConfiguration clientConfig =
174 getClientConfig(netconfClientSessionListener, netconfNode);
175 final ListenableFuture<NetconfDeviceCapabilities> future =
176 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
178 activeConnectors.put(nodeId, deviceCommunicatorDTO);
180 Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
182 public void onSuccess(final NetconfDeviceCapabilities result) {
183 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
187 public void onFailure(final Throwable throwable) {
188 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
189 // remove this node from active connectors?
191 }, MoreExecutors.directExecutor());
196 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
197 return createDeviceCommunicator(nodeId, node, null);
200 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
201 final NetconfNodeAugmentedOptional nodeOptional) {
202 final RemoteDeviceId remoteDeviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
204 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
205 RemoteDeviceHandler<NetconfSessionPreferences> salFacade = createSalFacade(remoteDeviceId);
206 if (keepaliveDelay > 0) {
207 LOG.info("Adding keepalive facade, for device {}", nodeId);
208 salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, this.keepaliveExecutor.getExecutor(),
209 keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
212 final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> device;
213 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
214 if (node.requireSchemaless()) {
215 device = new SchemalessNetconfDevice(baseSchemas, remoteDeviceId, salFacade);
216 yanglibRegistrations = List.of();
218 final boolean reconnectOnChangedSchema = node.requireReconnectOnChangedSchema();
219 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node, nodeId.getValue());
220 device = new NetconfDeviceBuilder()
221 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
222 .setSchemaResourcesDTO(resources)
223 .setGlobalProcessingExecutor(this.processingExecutor)
224 .setId(remoteDeviceId)
225 .setSalFacade(salFacade)
227 .setEventExecutor(eventExecutor)
228 .setNodeOptional(nodeOptional)
229 .setDeviceActionFactory(deviceActionFactory)
230 .setBaseSchemas(baseSchemas)
232 yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources);
235 final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
236 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
237 if (rpcMessageLimit < 1) {
238 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
241 final NetconfDeviceCommunicator netconfDeviceCommunicator =
242 userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
243 userCapabilities.get(), rpcMessageLimit)
244 : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
246 if (salFacade instanceof KeepaliveSalFacade) {
247 ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
250 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
253 private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
254 final NetconfNode node, final SchemaResourcesDTO resources) {
255 final YangLibrary yangLibrary = node.getYangLibrary();
256 if (yangLibrary != null) {
257 final Uri uri = yangLibrary.getYangLibraryUrl();
259 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
260 final String yangLibURL = uri.getValue();
261 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
263 // pre register yang library sources as fallback schemas to schema registry
264 final LibraryModulesSchemas schemas;
265 final String yangLibUsername = yangLibrary.getUsername();
266 final String yangLigPassword = yangLibrary.getPassword();
267 if (yangLibUsername != null && yangLigPassword != null) {
268 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
270 schemas = LibraryModulesSchemas.create(yangLibURL);
273 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
274 registrations.add(schemaRegistry.registerSchemaSource(new YangLibrarySchemaYangSourceProvider(
275 remoteDeviceId, schemas.getAvailableModels()),
276 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
277 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
279 return List.copyOf(registrations);
287 * Sets the private key path from location specified in configuration file using blueprint.
289 public void setPrivateKeyPath(final String privateKeyPath) {
290 this.privateKeyPath = privateKeyPath;
294 * Sets the private key passphrase from location specified in configuration file using blueprint.
296 public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
297 this.privateKeyPassphrase = privateKeyPassphrase;
300 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
301 final NetconfNode node) {
302 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
303 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
304 node.requireSleepFactor().decimalValue());
305 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
306 final Protocol protocol = node.getProtocol();
307 if (node.requireTcpOnly()) {
308 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
309 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
310 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
311 } else if (protocol == null || protocol.getName() == Name.SSH) {
312 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
313 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
314 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
315 } else if (protocol.getName() == Name.TLS) {
316 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
317 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
318 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
320 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
323 if (node.getOdlHelloMessageCapabilities() != null) {
324 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
325 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
328 return reconnectingClientConfigurationBuilder
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) {
338 if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
339 .rev150114.netconf.node.credentials.credentials.LoginPassword) {
340 final org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
341 .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword
342 = (org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
343 .rev150114.netconf.node.credentials.credentials.LoginPassword) credentials;
344 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
346 if (credentials instanceof LoginPwUnencrypted) {
347 final LoginPasswordUnencrypted loginPassword =
348 ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
349 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
351 if (credentials instanceof LoginPw) {
352 final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
353 return new LoginPasswordHandler(loginPassword.getUsername(),
354 encryptionService.decrypt(loginPassword.getPassword()));
356 if (credentials instanceof KeyAuth) {
357 final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
358 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
359 keystoreAdapter, encryptionService);
361 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
364 protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
366 private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
367 // if none of yang-module-capabilities or non-module-capabilities is specified
368 // just return absent
369 if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
370 return Optional.empty();
373 final List<String> capabilities = new ArrayList<>();
375 boolean overrideYangModuleCaps = false;
376 if (node.getYangModuleCapabilities() != null) {
377 capabilities.addAll(node.getYangModuleCapabilities().getCapability());
378 overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
381 //non-module capabilities should not exist in yang module capabilities
382 final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
383 Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
384 "List yang-module-capabilities/capability should contain only module based capabilities. "
385 + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
387 boolean overrideNonModuleCaps = false;
388 if (node.getNonModuleCapabilities() != null) {
389 capabilities.addAll(node.getNonModuleCapabilities().getCapability());
390 overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
393 return Optional.of(new UserPreferences(NetconfSessionPreferences
394 .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));