2903c026278b4d9b7f251b53bcd0bfbeaab140c1
[netconf.git] / netconf / netconf-topology / src / main / java / org / opendaylight / netconf / topology / spi / AbstractNetconfTopology.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.topology.spi;
9
10 import static java.util.Objects.requireNonNull;
11
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.URL;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
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;
84
85 public abstract class AbstractNetconfTopology implements NetconfTopology {
86     private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
87
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;
94
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<>();
104
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);
123
124         keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
125     }
126
127     @Override
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);
131     }
132
133     /**
134      * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
135      *
136      * @param nodeConfiguration Node configuration container.
137      * @return String representation of node configuration with credentials replaced by asterisks.
138      */
139     @VisibleForTesting
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, "***");
145     }
146
147     @Override
148     public ListenableFuture<Void> disconnectNode(final NodeId nodeId) {
149         LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue());
150
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"));
155         }
156
157         connectorDTO.close();
158         return Futures.immediateFuture(null);
159     }
160
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);
165
166         requireNonNull(netconfNode.getHost());
167         requireNonNull(netconfNode.getPort());
168
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);
176
177         activeConnectors.put(nodeId, deviceCommunicatorDTO);
178
179         Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
180             @Override
181             public void onSuccess(final NetconfDeviceCapabilities result) {
182                 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
183             }
184
185             @Override
186             public void onFailure(final Throwable throwable) {
187                 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
188                 // remove this node from active connectors?
189             }
190         }, MoreExecutors.directExecutor());
191
192         return future;
193     }
194
195     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
196         return createDeviceCommunicator(nodeId, node, null);
197     }
198
199     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
200             final NetconfNodeAugmentedOptional nodeOptional) {
201         final RemoteDeviceId remoteDeviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
202
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());
209         }
210
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();
216         } else {
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)
225                 .setNode(node)
226                 .setEventExecutor(eventExecutor)
227                 .setNodeOptional(nodeOptional)
228                 .setDeviceActionFactory(deviceActionFactory)
229                 .setBaseSchemas(baseSchemas)
230                 .build();
231             yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources);
232         }
233
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);
238         }
239
240         final NetconfDeviceCommunicator netconfDeviceCommunicator =
241              userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
242                      userCapabilities.get(), rpcMessageLimit)
243             : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
244
245         if (salFacade instanceof KeepaliveSalFacade) {
246             ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
247         }
248
249         return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
250     }
251
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();
257             if (uri != null) {
258                 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
259                 final String yangLibURL = uri.getValue();
260                 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
261
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);
268                 } else {
269                     schemas = LibraryModulesSchemas.create(yangLibURL);
270                 }
271
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())));
277                 }
278                 return List.copyOf(registrations);
279             }
280         }
281
282         return List.of();
283     }
284
285     /**
286      * Sets the private key path from location specified in configuration file using blueprint.
287      */
288     public void setPrivateKeyPath(final String privateKeyPath) {
289         this.privateKeyPath = privateKeyPath;
290     }
291
292     /**
293      * Sets the private key passphrase from location specified in configuration file using blueprint.
294      */
295     public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
296         this.privateKeyPassphrase = privateKeyPassphrase;
297     }
298
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);
318         } else {
319             throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
320         }
321
322         if (node.getOdlHelloMessageCapabilities() != null) {
323             reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
324                     Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
325         }
326
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)
334                 .build();
335     }
336
337     private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
338         if (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());
342         }
343         if (credentials instanceof LoginPwUnencrypted) {
344             final LoginPasswordUnencrypted loginPassword =
345                     ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
346             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
347         }
348         if (credentials instanceof LoginPw) {
349             final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
350             return new LoginPasswordHandler(loginPassword.getUsername(),
351                     encryptionService.decrypt(loginPassword.getPassword()));
352         }
353         if (credentials instanceof KeyAuth) {
354             final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
355             return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
356                     keystoreAdapter, encryptionService);
357         }
358         throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
359     }
360
361     protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
362
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();
368         }
369
370         final List<String> capabilities = new ArrayList<>();
371
372         boolean overrideYangModuleCaps = false;
373         if (node.getYangModuleCapabilities() != null) {
374             capabilities.addAll(node.getYangModuleCapabilities().getCapability());
375             overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
376         }
377
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());
383
384         boolean overrideNonModuleCaps = false;
385         if (node.getNonModuleCapabilities() != null) {
386             capabilities.addAll(node.getNonModuleCapabilities().getCapability());
387             overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
388         }
389
390         return Optional.of(new UserPreferences(NetconfSessionPreferences
391             .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));
392     }
393 }