Ditch NetconfNode from SchemaResourceManager
[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.getSchemaCacheDirectory(),
219                 nodeId.getValue());
220             device = new NetconfDeviceBuilder()
221                 .setReconnectOnSchemasChange(reconnectOnChangedSchema)
222                 .setSchemaResourcesDTO(resources)
223                 .setGlobalProcessingExecutor(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, final NodeId nodeId) {
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                 .withName(nodeId.getValue())
330                 .withAddress(NetconfNodeUtils.toInetSocketAddress(node))
331                 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava())
332                 .withReconnectStrategy(sf.createReconnectStrategy())
333                 .withConnectStrategyFactory(sf)
334                 .withSessionListener(listener)
335                 .build();
336     }
337
338     private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
339         if (credentials
340                 instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
341                     .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword) {
342             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
343         }
344         if (credentials instanceof LoginPwUnencrypted) {
345             final LoginPasswordUnencrypted loginPassword =
346                     ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
347             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
348         }
349         if (credentials instanceof LoginPw) {
350             final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
351             return new LoginPasswordHandler(loginPassword.getUsername(),
352                     encryptionService.decrypt(loginPassword.getPassword()));
353         }
354         if (credentials instanceof KeyAuth) {
355             final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
356             return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
357                     keystoreAdapter, encryptionService);
358         }
359         throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
360     }
361
362     protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
363
364     private static Optional<UserPreferences> getUserCapabilities(final NetconfNode node) {
365         // if none of yang-module-capabilities or non-module-capabilities is specified
366         // just return absent
367         if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
368             return Optional.empty();
369         }
370
371         final List<String> capabilities = new ArrayList<>();
372
373         boolean overrideYangModuleCaps = false;
374         if (node.getYangModuleCapabilities() != null) {
375             capabilities.addAll(node.getYangModuleCapabilities().getCapability());
376             overrideYangModuleCaps = node.getYangModuleCapabilities().getOverride();
377         }
378
379         //non-module capabilities should not exist in yang module capabilities
380         final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
381         Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
382                 "List yang-module-capabilities/capability should contain only module based capabilities. "
383                         + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
384
385         boolean overrideNonModuleCaps = false;
386         if (node.getNonModuleCapabilities() != null) {
387             capabilities.addAll(node.getNonModuleCapabilities().getCapability());
388             overrideNonModuleCaps = node.getNonModuleCapabilities().getOverride();
389         }
390
391         return Optional.of(new UserPreferences(NetconfSessionPreferences
392             .fromStrings(capabilities, CapabilityOrigin.UserDefined), overrideYangModuleCaps, overrideNonModuleCaps));
393     }
394 }