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.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.NetworkTopology;
77 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
78 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
79 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
80 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyBuilder;
81 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
82 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
83 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
84 import org.opendaylight.yangtools.yang.common.Empty;
85 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
86 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
87 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
88 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
89 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
90 import org.slf4j.Logger;
91 import org.slf4j.LoggerFactory;
93 public abstract class AbstractNetconfTopology implements NetconfTopology {
94 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
96 private final NetconfClientDispatcher clientDispatcher;
97 private final EventExecutor eventExecutor;
98 private final DeviceActionFactory deviceActionFactory;
99 private final NetconfKeystoreAdapter keystoreAdapter;
100 private final SchemaResourceManager schemaManager;
101 private final BaseNetconfSchemas baseSchemas;
103 protected final ScheduledThreadPool keepaliveExecutor;
104 protected final ListeningExecutorService processingExecutor;
105 protected final DataBroker dataBroker;
106 protected final DOMMountPointService mountPointService;
107 protected final String topologyId;
108 protected String privateKeyPath;
109 protected String privateKeyPassphrase;
110 protected final AAAEncryptionService encryptionService;
111 protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
113 protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
114 final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
115 final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
116 final DataBroker dataBroker, final DOMMountPointService mountPointService,
117 final AAAEncryptionService encryptionService,
118 final DeviceActionFactory deviceActionFactory,
119 final BaseNetconfSchemas baseSchemas) {
120 this.topologyId = requireNonNull(topologyId);
121 this.clientDispatcher = clientDispatcher;
122 this.eventExecutor = eventExecutor;
123 this.keepaliveExecutor = keepaliveExecutor;
124 this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
125 this.schemaManager = requireNonNull(schemaManager);
126 this.deviceActionFactory = deviceActionFactory;
127 this.dataBroker = requireNonNull(dataBroker);
128 this.mountPointService = mountPointService;
129 this.encryptionService = encryptionService;
130 this.baseSchemas = requireNonNull(baseSchemas);
132 keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
134 // FIXME: this should be a put(), as we are initializing and will be re-populating the datastore with all the
135 // devices. Whatever has been there before should be nuked to properly re-align lifecycle.
136 final var wtx = dataBroker.newWriteOnlyTransaction();
137 wtx.merge(LogicalDatastoreType.OPERATIONAL, InstanceIdentifier.builder(NetworkTopology.class)
138 .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
139 .build(), new TopologyBuilder().setTopologyId(new TopologyId(topologyId)).build());
140 final var future = wtx.commit();
143 } catch (InterruptedException | ExecutionException e) {
144 LOG.error("Unable to initialize topology {}", topologyId, e);
145 throw new IllegalStateException(e);
148 LOG.debug("Topology {} initialized", topologyId);
152 public ListenableFuture<Empty> connectNode(final NodeId nodeId, final Node configNode) {
153 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
154 return setupConnection(nodeId, configNode);
158 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
160 * @param nodeConfiguration Node configuration container.
161 * @return String representation of node configuration with credentials replaced by asterisks.
164 public static String hideCredentials(final Node nodeConfiguration) {
165 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
166 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
167 final String nodeConfigurationString = nodeConfiguration.toString();
168 return nodeConfigurationString.replace(nodeCredentials, "***");
172 public ListenableFuture<Empty> disconnectNode(final NodeId nodeId) {
173 final var nodeName = nodeId.getValue();
174 LOG.debug("Disconnecting RemoteDevice{{}}", nodeName);
176 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
177 if (connectorDTO == null) {
178 return Futures.immediateFailedFuture(
179 new IllegalStateException("Cannot disconnect " + nodeName + " as it is not connected"));
182 connectorDTO.close();
183 return Futures.immediateFuture(Empty.value());
186 protected ListenableFuture<Empty> setupConnection(final NodeId nodeId, final Node configNode) {
187 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
188 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
190 requireNonNull(netconfNode.getHost());
191 requireNonNull(netconfNode.getPort());
193 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
194 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
195 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
196 final NetconfReconnectingClientConfiguration clientConfig =
197 getClientConfig(netconfClientSessionListener, netconfNode, nodeId);
198 final ListenableFuture<Empty> future =
199 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
201 activeConnectors.put(nodeId, deviceCommunicatorDTO);
203 Futures.addCallback(future, new FutureCallback<>() {
205 public void onSuccess(final Empty result) {
206 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
210 public void onFailure(final Throwable throwable) {
211 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
212 // remove this node from active connectors?
214 }, MoreExecutors.directExecutor());
219 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
220 return createDeviceCommunicator(nodeId, node, null);
223 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
224 final NetconfNodeAugmentedOptional nodeOptional) {
225 final RemoteDeviceId remoteDeviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
227 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
228 RemoteDeviceHandler salFacade = createSalFacade(remoteDeviceId);
229 if (keepaliveDelay > 0) {
230 LOG.info("Adding keepalive facade, for device {}", nodeId);
231 salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(),
232 keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
235 final RemoteDevice<NetconfDeviceCommunicator> device;
236 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
237 if (node.requireSchemaless()) {
238 device = new SchemalessNetconfDevice(baseSchemas, remoteDeviceId, salFacade);
239 yanglibRegistrations = List.of();
241 final boolean reconnectOnChangedSchema = node.requireReconnectOnChangedSchema();
242 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(),
244 device = new NetconfDeviceBuilder()
245 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
246 .setSchemaResourcesDTO(resources)
247 .setGlobalProcessingExecutor(processingExecutor)
248 .setId(remoteDeviceId)
249 .setSalFacade(salFacade)
251 .setEventExecutor(eventExecutor)
252 .setNodeOptional(nodeOptional)
253 .setDeviceActionFactory(deviceActionFactory)
254 .setBaseSchemas(baseSchemas)
256 yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources);
259 final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
260 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
261 if (rpcMessageLimit < 1) {
262 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
265 final NetconfDeviceCommunicator netconfDeviceCommunicator =
266 userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
267 userCapabilities.get(), rpcMessageLimit)
268 : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
270 if (salFacade instanceof KeepaliveSalFacade) {
271 ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
274 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
277 private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
278 final NetconfNode node, final SchemaResourcesDTO resources) {
279 final YangLibrary yangLibrary = node.getYangLibrary();
280 if (yangLibrary != null) {
281 final Uri uri = yangLibrary.getYangLibraryUrl();
283 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
284 final String yangLibURL = uri.getValue();
285 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
287 // pre register yang library sources as fallback schemas to schema registry
288 final LibraryModulesSchemas schemas;
289 final String yangLibUsername = yangLibrary.getUsername();
290 final String yangLigPassword = yangLibrary.getPassword();
291 if (yangLibUsername != null && yangLigPassword != null) {
292 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
294 schemas = LibraryModulesSchemas.create(yangLibURL);
297 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
298 registrations.add(schemaRegistry.registerSchemaSource(new YangLibrarySchemaYangSourceProvider(
299 remoteDeviceId, schemas.getAvailableModels()),
300 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
301 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
303 return List.copyOf(registrations);
311 * Sets the private key path from location specified in configuration file using blueprint.
313 public void setPrivateKeyPath(final String privateKeyPath) {
314 this.privateKeyPath = privateKeyPath;
318 * Sets the private key passphrase from location specified in configuration file using blueprint.
320 public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
321 this.privateKeyPassphrase = privateKeyPassphrase;
324 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
325 final NetconfNode node, final NodeId nodeId) {
326 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
327 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
328 node.requireSleepFactor().decimalValue());
329 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
330 final Protocol protocol = node.getProtocol();
331 if (node.requireTcpOnly()) {
332 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
333 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
334 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
335 } else if (protocol == null || protocol.getName() == Name.SSH) {
336 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
337 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
338 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
339 } else if (protocol.getName() == Name.TLS) {
340 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
341 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
342 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
344 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
347 if (node.getOdlHelloMessageCapabilities() != null) {
348 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
349 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
352 return reconnectingClientConfigurationBuilder
353 .withName(nodeId.getValue())
354 .withAddress(NetconfNodeUtils.toInetSocketAddress(node))
355 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
356 .withReconnectStrategy(sf.createReconnectStrategy())
357 .withConnectStrategyFactory(sf)
358 .withSessionListener(listener)
362 private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
364 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
365 .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword) {
366 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
368 if (credentials instanceof LoginPwUnencrypted) {
369 final LoginPasswordUnencrypted loginPassword =
370 ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
371 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
373 if (credentials instanceof LoginPw) {
374 final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
375 return new LoginPasswordHandler(loginPassword.getUsername(),
376 encryptionService.decrypt(loginPassword.getPassword()));
378 if (credentials instanceof KeyAuth) {
379 final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
380 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
381 keystoreAdapter, encryptionService);
383 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
386 protected abstract RemoteDeviceHandler createSalFacade(RemoteDeviceId id);
388 private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
389 // if none of yang-module-capabilities or non-module-capabilities is specified
390 // just return absent
391 if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
392 return Optional.empty();
395 final List<String> capabilities = new ArrayList<>();
397 boolean overrideYangModuleCaps = false;
398 if (node.getYangModuleCapabilities() != null) {
399 capabilities.addAll(node.getYangModuleCapabilities().getCapability());
400 overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
403 //non-module capabilities should not exist in yang module capabilities
404 final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
405 Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
406 "List yang-module-capabilities/capability should contain only module based capabilities. "
407 + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
409 boolean overrideNonModuleCaps = false;
410 if (node.getNonModuleCapabilities() != null) {
411 capabilities.addAll(node.getNonModuleCapabilities().getCapability());
412 overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
415 return Optional.of(new UserPreferences(NetconfSessionPreferences
416 .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));