Centralize NetconfNode/InetSocketAddress conversion
[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.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;
85
86 public abstract class AbstractNetconfTopology implements NetconfTopology {
87     private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfTopology.class);
88
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;
95
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<>();
105
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);
124
125         this.keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
126     }
127
128     @Override
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);
132     }
133
134     /**
135      * Hiding of private credentials from node configuration (credentials data is replaced by asterisks).
136      *
137      * @param nodeConfiguration Node configuration container.
138      * @return String representation of node configuration with credentials replaced by asterisks.
139      */
140     @VisibleForTesting
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, "***");
146     }
147
148     @Override
149     public ListenableFuture<Void> disconnectNode(final NodeId nodeId) {
150         LOG.debug("Disconnecting RemoteDevice{{}}", nodeId.getValue());
151
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"));
156         }
157
158         connectorDTO.close();
159         return Futures.immediateFuture(null);
160     }
161
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);
166
167         requireNonNull(netconfNode.getHost());
168         requireNonNull(netconfNode.getPort());
169
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);
177
178         activeConnectors.put(nodeId, deviceCommunicatorDTO);
179
180         Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
181             @Override
182             public void onSuccess(final NetconfDeviceCapabilities result) {
183                 LOG.debug("Connector for {} started succesfully", nodeId.getValue());
184             }
185
186             @Override
187             public void onFailure(final Throwable throwable) {
188                 LOG.error("Connector for {} failed", nodeId.getValue(), throwable);
189                 // remove this node from active connectors?
190             }
191         }, MoreExecutors.directExecutor());
192
193         return future;
194     }
195
196     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node) {
197         return createDeviceCommunicator(nodeId, node, null);
198     }
199
200     protected NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
201             final NetconfNodeAugmentedOptional nodeOptional) {
202         final RemoteDeviceId remoteDeviceId = NetconfNodeUtils.toRemoteDeviceId(nodeId, node);
203
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());
210         }
211
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();
217         } else {
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)
226                 .setNode(node)
227                 .setEventExecutor(eventExecutor)
228                 .setNodeOptional(nodeOptional)
229                 .setDeviceActionFactory(deviceActionFactory)
230                 .setBaseSchemas(baseSchemas)
231                 .build();
232             yanglibRegistrations = registerDeviceSchemaSources(remoteDeviceId, node, resources);
233         }
234
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);
239         }
240
241         final NetconfDeviceCommunicator netconfDeviceCommunicator =
242              userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
243                      userCapabilities.get(), rpcMessageLimit)
244             : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
245
246         if (salFacade instanceof KeepaliveSalFacade) {
247             ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
248         }
249
250         return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade, yanglibRegistrations);
251     }
252
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();
258             if (uri != null) {
259                 final List<SchemaSourceRegistration<?>> registrations = new ArrayList<>();
260                 final String yangLibURL = uri.getValue();
261                 final SchemaSourceRegistry schemaRegistry = resources.getSchemaRegistry();
262
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);
269                 } else {
270                     schemas = LibraryModulesSchemas.create(yangLibURL);
271                 }
272
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())));
278                 }
279                 return List.copyOf(registrations);
280             }
281         }
282
283         return List.of();
284     }
285
286     /**
287      * Sets the private key path from location specified in configuration file using blueprint.
288      */
289     public void setPrivateKeyPath(final String privateKeyPath) {
290         this.privateKeyPath = privateKeyPath;
291     }
292
293     /**
294      * Sets the private key passphrase from location specified in configuration file using blueprint.
295      */
296     public void setPrivateKeyPassphrase(final String privateKeyPassphrase) {
297         this.privateKeyPassphrase = privateKeyPassphrase;
298     }
299
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);
319         } else {
320             throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
321         }
322
323         if (node.getOdlHelloMessageCapabilities() != null) {
324             reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(
325                     Lists.newArrayList(node.getOdlHelloMessageCapabilities().getCapability()));
326         }
327
328         return reconnectingClientConfigurationBuilder
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 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());
345         }
346         if (credentials instanceof LoginPwUnencrypted) {
347             final LoginPasswordUnencrypted loginPassword =
348                     ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
349             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
350         }
351         if (credentials instanceof LoginPw) {
352             final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
353             return new LoginPasswordHandler(loginPassword.getUsername(),
354                     encryptionService.decrypt(loginPassword.getPassword()));
355         }
356         if (credentials instanceof KeyAuth) {
357             final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
358             return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
359                     keystoreAdapter, encryptionService);
360         }
361         throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
362     }
363
364     protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
365
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();
371         }
372
373         final List<String> capabilities = new ArrayList<>();
374
375         boolean overrideYangModuleCaps = false;
376         if (node.getYangModuleCapabilities() != null) {
377             capabilities.addAll(node.getYangModuleCapabilities().getCapability());
378             overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
379         }
380
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());
386
387         boolean overrideNonModuleCaps = false;
388         if (node.getNonModuleCapabilities() != null) {
389             capabilities.addAll(node.getNonModuleCapabilities().getCapability());
390             overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
391         }
392
393         return Optional.of(new UserPreferences(NetconfSessionPreferences
394             .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));
395     }
396 }