Make netconf node tcp-only leaf have default value
[netconf.git] / netconf / netconf-topology-singleton / src / main / java / org / opendaylight / netconf / topology / singleton / impl / RemoteDeviceConnectorImpl.java
1 /*
2  * Copyright (c) 2016 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
9 package org.opendaylight.netconf.topology.singleton.impl;
10
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Preconditions;
13 import com.google.common.collect.Lists;
14 import com.google.common.util.concurrent.FutureCallback;
15 import com.google.common.util.concurrent.Futures;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import com.google.common.util.concurrent.MoreExecutors;
18 import java.math.BigDecimal;
19 import java.net.InetSocketAddress;
20 import java.net.URL;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Objects;
25 import java.util.Optional;
26 import javax.annotation.Nullable;
27 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
28 import org.opendaylight.netconf.api.NetconfMessage;
29 import org.opendaylight.netconf.client.NetconfClientSessionListener;
30 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
31 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
32 import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
33 import org.opendaylight.netconf.nettyutil.ReconnectStrategyFactory;
34 import org.opendaylight.netconf.nettyutil.TimedReconnectStrategyFactory;
35 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
36 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
37 import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
38 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
39 import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
40 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
41 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
42 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
43 import org.opendaylight.netconf.sal.connect.netconf.auth.DatastoreBackedPublicKeyAuth;
44 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
45 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
46 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
47 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
48 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
49 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
50 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
51 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
52 import org.opendaylight.netconf.sal.connect.util.SslHandlerFactoryImpl;
53 import org.opendaylight.netconf.topology.singleton.api.RemoteDeviceConnector;
54 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfConnectorDTO;
55 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologySetup;
56 import org.opendaylight.netconf.topology.singleton.impl.utils.NetconfTopologyUtils;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.OdlHelloMessageCapabilities;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.parameters.Protocol;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyAuth;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
71 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
72 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
73 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
74 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
75 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
78
79 public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
80
81     private static final Logger LOG = LoggerFactory.getLogger(RemoteDeviceConnectorImpl.class);
82
83     // Initializes default constant instances for the case when the default schema repository
84     // directory cache/schema is used.
85
86     private final NetconfTopologySetup netconfTopologyDeviceSetup;
87     private final RemoteDeviceId remoteDeviceId;
88     private final String privateKeyPath;
89     private final String privateKeyPassphrase;
90     private final AAAEncryptionService encryptionService;
91     private NetconfConnectorDTO deviceCommunicatorDTO;
92     private final NetconfKeystoreAdapter keystoreAdapter;
93
94     public RemoteDeviceConnectorImpl(final NetconfTopologySetup netconfTopologyDeviceSetup,
95                                      final RemoteDeviceId remoteDeviceId) {
96
97         this.netconfTopologyDeviceSetup = Preconditions.checkNotNull(netconfTopologyDeviceSetup);
98         this.remoteDeviceId = remoteDeviceId;
99         this.privateKeyPath = netconfTopologyDeviceSetup.getPrivateKeyPath();
100         this.privateKeyPassphrase = netconfTopologyDeviceSetup.getPrivateKeyPassphrase();
101         this.encryptionService = netconfTopologyDeviceSetup.getEncryptionService();
102         keystoreAdapter = new NetconfKeystoreAdapter(netconfTopologyDeviceSetup.getDataBroker());
103     }
104
105     @Override
106     public void startRemoteDeviceConnection(final RemoteDeviceHandler<NetconfSessionPreferences> deviceHandler) {
107
108         final NetconfNode netconfNode = netconfTopologyDeviceSetup.getNode().augmentation(NetconfNode.class);
109         final NodeId nodeId = netconfTopologyDeviceSetup.getNode().getNodeId();
110         Preconditions.checkNotNull(netconfNode.getHost());
111         Preconditions.checkNotNull(netconfNode.getPort());
112
113         this.deviceCommunicatorDTO = createDeviceCommunicator(nodeId, netconfNode, deviceHandler);
114         final NetconfDeviceCommunicator deviceCommunicator = deviceCommunicatorDTO.getCommunicator();
115         final NetconfClientSessionListener netconfClientSessionListener = deviceCommunicatorDTO.getSessionListener();
116         final NetconfReconnectingClientConfiguration clientConfig =
117                 getClientConfig(netconfClientSessionListener, netconfNode);
118         final ListenableFuture<NetconfDeviceCapabilities> future = deviceCommunicator
119                 .initializeRemoteConnection(netconfTopologyDeviceSetup.getNetconfClientDispatcher(), clientConfig);
120
121         Futures.addCallback(future, new FutureCallback<NetconfDeviceCapabilities>() {
122             @Override
123             public void onSuccess(final NetconfDeviceCapabilities result) {
124                 LOG.debug("{}: Connector started successfully", remoteDeviceId);
125             }
126
127             @Override
128             public void onFailure(@Nullable final Throwable throwable) {
129                 LOG.error("{}: Connector failed", remoteDeviceId, throwable);
130             }
131         }, MoreExecutors.directExecutor());
132     }
133
134     @SuppressWarnings("checkstyle:IllegalCatch")
135     @Override
136     public void stopRemoteDeviceConnection() {
137         if (deviceCommunicatorDTO != null) {
138             try {
139                 deviceCommunicatorDTO.close();
140             } catch (final Exception e) {
141                 LOG.error("{}: Error at closing device communicator.", remoteDeviceId, e);
142             }
143         }
144     }
145
146     @VisibleForTesting
147     NetconfConnectorDTO createDeviceCommunicator(final NodeId nodeId, final NetconfNode node,
148                                                  final RemoteDeviceHandler<NetconfSessionPreferences> deviceHandler) {
149         //setup default values since default value is not supported in mdsal
150         final long defaultRequestTimeoutMillis = node.getDefaultRequestTimeoutMillis() == null
151                 ? NetconfTopologyUtils.DEFAULT_REQUEST_TIMEOUT_MILLIS : node.getDefaultRequestTimeoutMillis();
152         final long keepaliveDelay = node.getKeepaliveDelay() == null
153                 ? NetconfTopologyUtils.DEFAULT_KEEPALIVE_DELAY : node.getKeepaliveDelay();
154         final boolean reconnectOnChangedSchema = node.isReconnectOnChangedSchema() == null
155                 ? NetconfTopologyUtils.DEFAULT_RECONNECT_ON_CHANGED_SCHEMA : node.isReconnectOnChangedSchema();
156
157         RemoteDeviceHandler<NetconfSessionPreferences> salFacade = deviceHandler;
158         if (keepaliveDelay > 0) {
159             LOG.info("{}: Adding keepalive facade.", remoteDeviceId);
160             salFacade = new KeepaliveSalFacade(remoteDeviceId, salFacade,
161                     netconfTopologyDeviceSetup.getKeepaliveExecutor(), keepaliveDelay,
162                     defaultRequestTimeoutMillis);
163         }
164
165         final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = netconfTopologyDeviceSetup.getSchemaResourcesDTO();
166
167         // pre register yang library sources as fallback schemas to schema registry
168         final List<SchemaSourceRegistration<YangTextSchemaSource>> registeredYangLibSources = Lists.newArrayList();
169         if (node.getYangLibrary() != null) {
170             final String yangLibURL = node.getYangLibrary().getYangLibraryUrl().getValue();
171             final String yangLibUsername = node.getYangLibrary().getUsername();
172             final String yangLigPassword = node.getYangLibrary().getPassword();
173
174             final LibraryModulesSchemas libraryModulesSchemas;
175             if (yangLibURL != null) {
176                 if (yangLibUsername != null && yangLigPassword != null) {
177                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL, yangLibUsername, yangLigPassword);
178                 } else {
179                     libraryModulesSchemas = LibraryModulesSchemas.create(yangLibURL);
180                 }
181
182                 for (final Map.Entry<SourceIdentifier, URL> sourceIdentifierURLEntry :
183                         libraryModulesSchemas.getAvailableModels().entrySet()) {
184                     registeredYangLibSources
185                             .add(schemaResourcesDTO.getSchemaRegistry().registerSchemaSource(
186                                     new YangLibrarySchemaYangSourceProvider(remoteDeviceId,
187                                             libraryModulesSchemas.getAvailableModels()),
188                                     PotentialSchemaSource
189                                             .create(sourceIdentifierURLEntry.getKey(), YangTextSchemaSource.class,
190                                                     PotentialSchemaSource.Costs.REMOTE_IO.getValue())));
191                 }
192             }
193         }
194
195         final RemoteDevice<NetconfSessionPreferences, NetconfMessage, NetconfDeviceCommunicator> device;
196         if (node.isSchemaless()) {
197             device = new SchemalessNetconfDevice(remoteDeviceId, salFacade);
198         } else {
199             device = new NetconfDeviceBuilder()
200                     .setReconnectOnSchemasChange(reconnectOnChangedSchema)
201                     .setSchemaResourcesDTO(schemaResourcesDTO)
202                     .setGlobalProcessingExecutor(netconfTopologyDeviceSetup.getProcessingExecutor())
203                     .setId(remoteDeviceId)
204                     .setSalFacade(salFacade)
205                     .build();
206         }
207
208         final Optional<NetconfSessionPreferences> userCapabilities = getUserCapabilities(node);
209         final int rpcMessageLimit =
210                 node.getConcurrentRpcLimit() == null
211                         ? NetconfTopologyUtils.DEFAULT_CONCURRENT_RPC_LIMIT : node.getConcurrentRpcLimit();
212
213         if (rpcMessageLimit < 1) {
214             LOG.info("{}: Concurrent rpc limit is smaller than 1, no limit will be enforced.", remoteDeviceId);
215         }
216
217         NetconfDeviceCommunicator netconfDeviceCommunicator =
218              userCapabilities.isPresent() ? new NetconfDeviceCommunicator(remoteDeviceId, device,
219              new UserPreferences(userCapabilities.get(),
220                  Objects.isNull(node.getYangModuleCapabilities())
221                          ? false : node.getYangModuleCapabilities().isOverride(),
222                  Objects.isNull(node.getNonModuleCapabilities())
223                          ? false : node.getNonModuleCapabilities().isOverride()), rpcMessageLimit)
224             : new NetconfDeviceCommunicator(remoteDeviceId, device, rpcMessageLimit);
225
226         if (salFacade instanceof KeepaliveSalFacade) {
227             ((KeepaliveSalFacade)salFacade).setListener(netconfDeviceCommunicator);
228         }
229         return new NetconfConnectorDTO(netconfDeviceCommunicator, salFacade);
230     }
231
232     private static Optional<NetconfSessionPreferences> getUserCapabilities(final NetconfNode node) {
233         if (node.getYangModuleCapabilities() == null && node.getNonModuleCapabilities() == null) {
234             return Optional.empty();
235         }
236         final List<String> capabilities = new ArrayList<>();
237
238         if (node.getYangModuleCapabilities() != null) {
239             capabilities.addAll(node.getYangModuleCapabilities().getCapability());
240         }
241
242         //non-module capabilities should not exist in yang module capabilities
243         final NetconfSessionPreferences netconfSessionPreferences = NetconfSessionPreferences.fromStrings(capabilities);
244         Preconditions.checkState(netconfSessionPreferences.getNonModuleCaps().isEmpty(),
245                 "List yang-module-capabilities/capability should contain only module based capabilities. "
246                         + "Non-module capabilities used: " + netconfSessionPreferences.getNonModuleCaps());
247
248         if (node.getNonModuleCapabilities() != null) {
249             capabilities.addAll(node.getNonModuleCapabilities().getCapability());
250         }
251
252         return Optional.of(NetconfSessionPreferences.fromStrings(capabilities, CapabilityOrigin.UserDefined));
253     }
254
255     //TODO: duplicate code
256     private static InetSocketAddress getSocketAddress(final Host host, final int port) {
257         if (host.getDomainName() != null) {
258             return new InetSocketAddress(host.getDomainName().getValue(), port);
259         } else {
260             final IpAddress ipAddress = host.getIpAddress();
261             final String ip = ipAddress.getIpv4Address() != null ? ipAddress.getIpv4Address().getValue() :
262                     ipAddress.getIpv6Address().getValue();
263             return new InetSocketAddress(ip, port);
264         }
265     }
266
267     @VisibleForTesting
268     NetconfReconnectingClientConfiguration getClientConfig(final NetconfClientSessionListener listener,
269                                                            final NetconfNode node) {
270
271         //setup default values since default value is not supported in mdsal
272         final long clientConnectionTimeoutMillis = node.getConnectionTimeoutMillis() == null
273                 ? NetconfTopologyUtils.DEFAULT_CONNECTION_TIMEOUT_MILLIS : node.getConnectionTimeoutMillis();
274         final long maxConnectionAttempts = node.getMaxConnectionAttempts() == null
275                 ? NetconfTopologyUtils.DEFAULT_MAX_CONNECTION_ATTEMPTS : node.getMaxConnectionAttempts();
276         final int betweenAttemptsTimeoutMillis = node.getBetweenAttemptsTimeoutMillis() == null
277                 ? NetconfTopologyUtils.DEFAULT_BETWEEN_ATTEMPTS_TIMEOUT_MILLIS : node.getBetweenAttemptsTimeoutMillis();
278         final boolean isTcpOnly = node.isTcpOnly() == null
279                 ? NetconfTopologyUtils.DEFAULT_IS_TCP_ONLY : node.isTcpOnly();
280         final BigDecimal sleepFactor = node.getSleepFactor() == null
281                 ? NetconfTopologyUtils.DEFAULT_SLEEP_FACTOR : node.getSleepFactor();
282
283         final InetSocketAddress socketAddress = getSocketAddress(node.getHost(), node.getPort().getValue());
284
285         final ReconnectStrategyFactory sf =
286                 new TimedReconnectStrategyFactory(netconfTopologyDeviceSetup.getEventExecutor(), maxConnectionAttempts,
287                         betweenAttemptsTimeoutMillis, sleepFactor);
288
289
290         final NetconfReconnectingClientConfigurationBuilder reconnectingClientConfigurationBuilder;
291         final Protocol protocol = node.getProtocol();
292         if (isTcpOnly) {
293             reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
294                     .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
295                     .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
296         } else if (protocol == null || protocol.getName() == Protocol.Name.SSH) {
297             reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
298                     .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
299                     .withAuthHandler(getHandlerFromCredentials(node.getCredentials()));
300         } else if (protocol.getName() == Protocol.Name.TLS) {
301             reconnectingClientConfigurationBuilder = NetconfReconnectingClientConfigurationBuilder.create()
302                     .withSslHandlerFactory(new SslHandlerFactoryImpl(keystoreAdapter, protocol.getSpecification()))
303                     .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TLS);
304         } else {
305             throw new IllegalStateException("Unsupported protocol type: " + protocol.getName());
306         }
307
308         final List<Uri> odlHelloCapabilities = getOdlHelloCapabilities(node);
309         if (odlHelloCapabilities != null) {
310             reconnectingClientConfigurationBuilder.withOdlHelloCapabilities(odlHelloCapabilities);
311         }
312
313         return reconnectingClientConfigurationBuilder
314                 .withAddress(socketAddress)
315                 .withConnectionTimeoutMillis(clientConnectionTimeoutMillis)
316                 .withReconnectStrategy(sf.createReconnectStrategy())
317                 .withConnectStrategyFactory(sf)
318                 .withSessionListener(listener)
319                 .build();
320     }
321
322     private static List<Uri> getOdlHelloCapabilities(final NetconfNode node) {
323         final OdlHelloMessageCapabilities helloCapabilities = node.getOdlHelloMessageCapabilities();
324         return helloCapabilities != null ? helloCapabilities.getCapability() : null;
325     }
326
327     private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
328         if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
329                 .rev150114.netconf.node.credentials.credentials.LoginPassword) {
330             final org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
331                     .rev150114.netconf.node.credentials.credentials.LoginPassword loginPassword
332                     = (org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology
333                     .rev150114.netconf.node.credentials.credentials.LoginPassword) credentials;
334             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
335         }
336         if (credentials instanceof LoginPwUnencrypted) {
337             final LoginPasswordUnencrypted loginPassword =
338                     ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
339             return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
340         }
341         if (credentials instanceof LoginPw) {
342             final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
343             return new LoginPasswordHandler(loginPassword.getUsername(),
344                     encryptionService.decrypt(loginPassword.getPassword()));
345         }
346         if (credentials instanceof KeyAuth) {
347             final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
348             return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
349                     keystoreAdapter, encryptionService);
350         }
351         throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
352     }
353 }