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.NetconfDeviceCommunicator;
51 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
52 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
53 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
54 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
55 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
56 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseNetconfSchemas;
57 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
58 import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
59 import org.opendaylight.netconf.topology.api.NetconfTopology;
60 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev190614.NetconfNodeAugmentedOptional;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol.Name;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.schema.storage.YangLibrary;
74 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
75 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
76 import org.opendaylight.yangtools.yang.common.Empty;
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<Empty> 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<Empty> 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(Empty.value());
161 protected ListenableFuture<Empty> setupConnection(final NodeId nodeId, final Node configNode) {
162 final NetconfNode netconfNode = configNode.augmentation(NetconfNode.class);
163 final NetconfNodeAugmentedOptional nodeOptional = configNode.augmentation(NetconfNodeAugmentedOptional.class);
165 requireNonNull(netconfNode.getHost());
166 requireNonNull(netconfNode.getPort());
168 final NetconfConnectorDTO deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, nodeOptional);
169 final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
170 final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
171 final NetconfReconnectingClientConfiguration clientConfig =
172 getClientConfig(netconfClientSessionListener, netconfNode, nodeId);
173 final ListenableFuture<Empty> future =
174 deviceCommunicator.initializeRemoteConnection(clientDispatcher, clientConfig);
176 activeConnectors.put(nodeId, deviceCommunicatorDTO);
178 Futures.addCallback(future, new FutureCallback<>() {
180 public void onSuccess(final Empty result) {
181 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
185 public void onFailure(final Throwable throwable) {
186 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
187 // remove this node from active connectors?
189 }, MoreExecutors.directExecutor());
194 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
195 return createDeviceCommunicator(nodeId, node, null);
198 protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
199 final NetconfNodeAugmentedOptional nodeOptional) {
200 final RemoteDeviceId remoteDeviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
202 final long keepaliveDelay = node.requireKeepaliveDelay().toJava();
203 RemoteDeviceHandler salFacade = createSalFacade(remoteDeviceId);
204 if (keepaliveDelay > 0) {
205 LOG.info("Adding keepalive facade, for device {}", nodeId);
206 salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade, keepaliveExecutor.getExecutor(),
207 keepaliveDelay, node.requireDefaultRequestTimeoutMillis().toJava());
210 final RemoteDevice<NetconfDeviceCommunicator> device;
211 final List<SchemaSourceRegistration<?>> yanglibRegistrations;
212 if (node.requireSchemaless()) {
213 device = new SchemalessNetconfDevice(baseSchemas, remoteDeviceId, salFacade);
214 yanglibRegistrations = List.of();
216 final boolean reconnectOnChangedSchema = node.requireReconnectOnChangedSchema();
217 final SchemaResourcesDTO resources = schemaManager.getSchemaResources(node.getSchemaCacheDirectory(),
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 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));