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.client.NetconfClientDispatcher;
33 import org.opendaylight.netconf.client.NetconfClientSessionListener;
34 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
35 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
36 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
37 import org.opendaylight.netconf.nettyutil.ReconnectStrategyFactory;
38 import org.opendaylight.netconf.nettyutil.TimedReconnectStrategyFactory;
39 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
40 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
41 import org.opendaylight.netconf.sal.connect.api.DeviceActionFactory;
42 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
43 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
44 import org.opendaylight.netconf.sal.connect.api.SchemaResourceManager;
45 import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
46 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice.SchemaResourcesDTO;
47 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
48 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
49 import org.opendaylight.netconf.sal.connect.netconf.auth.DatastoreBackedPublicKeyAuth;
50 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
51 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
52 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
53 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
54 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
55 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
56 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
57 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseNetconfSchemas;
58 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
59 import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
60 import org.opendaylight.netconf.topology.api.NetconfTopology;
61 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.schema.storage.YangLibrary;
75 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
76 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
77 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
78 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
79 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
80 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
81 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
85 public abstract class AbstractNetconfTopology implements NetconfTopology {
86 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
88 private final NetconfClientDispatcher clientDispatcher;
89 private final EventExecutor eventExecutor;
90 private final DeviceActionFactory deviceActionFactory;
91 private final NetconfKeystoreAdapter keystoreAdapter;
92 private final SchemaResourceManager schemaManager;
93 private final BaseNetconfSchemas baseSchemas;
95 protected final ScheduledThreadPool keepaliveExecutor;
96 protected final ListeningExecutorService processingExecutor;
97 protected final DataBroker dataBroker;
98 protected final DOMMountPointService mountPointService;
99 protected final String topologyId;
100 protected String privateKeyPath;
101 protected String privateKeyPassphrase;
102 protected final AAAEncryptionService encryptionService;
103 protected final HashMap<NodeId, NetconfConnectorDTO> activeConnectors = new HashMap<>();
105 protected AbstractNetconfTopology(final String topologyId, final NetconfClientDispatcher clientDispatcher,
106 final EventExecutor eventExecutor, final ScheduledThreadPool keepaliveExecutor,
107 final ThreadPool processingExecutor, final SchemaResourceManager schemaManager,
108 final DataBroker dataBroker, final DOMMountPointService mountPointService,
109 final AAAEncryptionService encryptionService,
110 final DeviceActionFactory deviceActionFactory,
111 final BaseNetconfSchemas baseSchemas) {
112 this.topologyId = topologyId;
113 this.clientDispatcher = clientDispatcher;
114 this.eventExecutor = eventExecutor;
115 this.keepaliveExecutor = keepaliveExecutor;
116 this.processingExecutor = MoreExecutors.listeningDecorator(processingExecutor.getExecutor());
117 this.schemaManager = requireNonNull(schemaManager);
118 this.deviceActionFactory = deviceActionFactory;
119 this.dataBroker = dataBroker;
120 this.mountPointService = mountPointService;
121 this.encryptionService = encryptionService;
122 this.baseSchemas = requireNonNull(baseSchemas);
124 keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
128 public ListenableFuture<NetconfDeviceCapabilities> connectNode(final NodeId nodeId, final Node configNode) {
129 LOG.info("Connecting RemoteDevice{{}} , with config {}", nodeId, hideCredentials(configNode));
130 return setupConnection(nodeId, configNode);
134 * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
136 * @param nodeConfiguration Node configuration container.
137 * @return String representation of node configuration with credentials replaced by asterisks.
140 public static String hideCredentials(final Node nodeConfiguration) {
141 final NetconfNode netconfNodeAugmentation = nodeConfiguration.augmentation(NetconfNode.class);
142 final String nodeCredentials = netconfNodeAugmentation.getCredentials().toString();
143 final String nodeConfigurationString = nodeConfiguration.toString();
144 return nodeConfigurationString.replace(nodeCredentials, "***");
148 public ListenableFuture<Void> disconnectNode(final NodeId nodeId) {
149 LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue());
151 final NetconfConnectorDTO connectorDTO = activeConnectors.remove(nodeId);
152 if (connectorDTO == null) {
153 return Futures.immediateFailedFuture(
154 new IllegalStateException("Unable to disconnect device that is not connected"));
157 connectorDTO.close();
158 return Futures.immediateFuture(null);
161 protected ListenableFuture<NetconfDeviceCapabilities> setupConnection(final NodeId nodeId,
162 final Node configNode) {
163 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
164 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
166 requireNonNull(netconfNode.getHost());
167 requireNonNull(netconfNode.getPort());
169 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
170 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
171 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
172 final NetconfReconnectingClientConfiguration clientConfig =
173 getClientConfig(netconfClientSessionListener, netconfNode, nodeId);
174 final ListenableFuture<NetconfDeviceCapabilities> future =
175 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
177 activeConnectors.put(nodeId, deviceCommunicatorDTO);
179 Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
181 public void onSuccess(final NetconfDeviceCapabilities result) {
182 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
186 public void onFailure(final Throwable throwable) {
187 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
188 // remove this node from active connectors?
190 }, MoreExecutors.directExecutor());
195 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
196 return createDeviceCommunicator(nodeId, node, null);
199 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
200 final NetconfNodeAugmentedOptional nodeOptional) {
201 final RemoteDeviceId remoteDeviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
203 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
204 RemoteDeviceHandler<NetconfSessionPreferences> salFacade = createSalFacade(remoteDeviceId);
205 if (keepaliveDelay > 0) {
206 LOG.info("Adding keepalive facade, for device {}", nodeId);
207 salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(),
208 keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
211 final RemoteDevice<NetconfSessionPreferences, NetconfDeviceCommunicator> device;
212 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
213 if (node.requireSchemaless()) {
214 device = new SchemalessNetconfDevice(baseSchemas, remoteDeviceId, salFacade);
215 yanglibRegistrations = List.of();
217 final boolean reconnectOnChangedSchema = node.requireReconnectOnChangedSchema();
218 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node, nodeId.getValue());
219 device = new NetconfDeviceBuilder()
220 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
221 .setSchemaResourcesDTO(resources)
222 .setGlobalProcessingExecutor(processingExecutor)
223 .setId(remoteDeviceId)
224 .setSalFacade(salFacade)
226 .setEventExecutor(eventExecutor)
227 .setNodeOptional(nodeOptional)
228 .setDeviceActionFactory(deviceActionFactory)
229 .setBaseSchemas(baseSchemas)
231 yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources);
234 final Optional<UserPreferences> userCapabilities = getUserCapabilities(node);
235 final int rpcMessageLimit = node.requireConcurrentRpcLimit().toJava();
236 if (rpcMessageLimit < 1) {
237 LOG.info("Concurrent rpc limit is smaller than 1, no limit will be enforced for device {}", remoteDeviceId);
240 final NetconfDeviceCommunicator netconfDeviceCommunicator =
241 userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
242 userCapabilities.get(), rpcMessageLimit)
243 : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
245 if (salFacade instanceof KeepaliveSalFacade) {
246 ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
249 return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
252 private static List<SchemaSourceRegistration<?>> registerDeviceSchemaSources(final RemoteDeviceId remoteDeviceId,
253 final NetconfNode node, final SchemaResourcesDTO resources) {
254 final YangLibrary yangLibrary = node.getYangLibrary();
255 if (yangLibrary != null) {
256 final Uri uri = yangLibrary.getYangLibraryUrl();
258 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
259 final String yangLibURL = uri.getValue();
260 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
262 // pre register yang library sources as fallback schemas to schema registry
263 final LibraryModulesSchemas schemas;
264 final String yangLibUsername = yangLibrary.getUsername();
265 final String yangLigPassword = yangLibrary.getPassword();
266 if (yangLibUsername != null && yangLigPassword != null) {
267 schemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
269 schemas = LibraryModulesSchemas.create(yangLibURL);
272 for (final Map.Entry<SourceIdentifier, URL> entry : schemas.getAvailableModels().entrySet()) {
273 registrations.add(schemaRegistry.registerSchemaSource(new YangLibrarySchemaYangSourceProvider(
274 remoteDeviceId, schemas.getAvailableModels()),
275 PotentialSchemaSource.create(entry.getKey(), YangTextSchemaSource.class,
276 PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
278 return List.copyOf(registrations);
286 * Sets the private key path from location specified in configuration file using blueprint.
288 public void setPrivateKeyPath(final String privateKeyPath) {
289 this.privateKeyPath = privateKeyPath;
293 * Sets the private key passphrase from location specified in configuration file using blueprint.
295 public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
296 this.privateKeyPassphrase = privateKeyPassphrase;
299 public NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
300 final NetconfNode node, final NodeId nodeId) {
301 final ReconnectStrategyFactory sf = new TimedReconnectStrategyFactory(eventExecutor,
302 node.requireMaxConnectionAttempts().toJava(), node.requireBetweenAttemptsTimeoutMillis().toJava(),
303 node.requireSleepFactor().decimalValue());
304 final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
305 final Protocol protocol = node.getProtocol();
306 if (node.requireTcpOnly()) {
307 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
308 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
309 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
310 } else if (protocol == null || protocol.getName() == Name.SSH) {
311 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
312 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
313 .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
314 } else if (protocol.getName() == Name.TLS) {
315 reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
316 .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
317 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
319 throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
322 if (node.getOdlHelloMessageCapabilities() != null) {
323 reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
324 Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
327 return reconnectingClientConfigurationBuilder
328 .withName(nodeId.getValue())
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) {
339 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
340 .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword) {
341 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
343 if (credentials instanceof LoginPwUnencrypted) {
344 final LoginPasswordUnencrypted loginPassword =
345 ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
346 return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
348 if (credentials instanceof LoginPw) {
349 final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
350 return new LoginPasswordHandler(loginPassword.getUsername(),
351 encryptionService.decrypt(loginPassword.getPassword()));
353 if (credentials instanceof KeyAuth) {
354 final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
355 return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
356 keystoreAdapter, encryptionService);
358 throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
361 protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
363 private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
364 // if none of yang-module-capabilities or non-module-capabilities is specified
365 // just return absent
366 if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
367 return Optional.empty();
370 final List<String> capabilities = new ArrayList<>();
372 boolean overrideYangModuleCaps = false;
373 if (node.getYangModuleCapabilities() != null) {
374 capabilities.addAll(node.getYangModuleCapabilities().getCapability());
375 overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
378 //non-module capabilities should not exist in yang module capabilities
379 final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
380 Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
381 "List yang-module-capabilities/capability should contain only module based capabilities. "
382 + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
384 boolean overrideNonModuleCaps = false;
385 if (node.getNonModuleCapabilities() != null) {
386 capabilities.addAll(node.getNonModuleCapabilities().getCapability());
387 overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
390 return Optional.of(new UserPreferences(NetconfSessionPreferences
391 .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));