Use SettableFuture instead of Promise
[netconf.git] / protocol / netconf-client / src / main / java / org / opendaylight / netconf / client / NetconfClientFactoryImpl.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech s.r.o. 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.client;
9
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol.SSH;
12 import static org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol.TCP;
13 import static org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol.TLS;
14
15 import com.google.common.collect.ImmutableSet;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import com.google.common.util.concurrent.SettableFuture;
18 import io.netty.util.HashedWheelTimer;
19 import io.netty.util.Timer;
20 import javax.inject.Singleton;
21 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
22 import org.opendaylight.netconf.transport.api.TransportChannel;
23 import org.opendaylight.netconf.transport.api.TransportChannelListener;
24 import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
25 import org.opendaylight.netconf.transport.ssh.SSHTransportStackFactory;
26 import org.opendaylight.netconf.transport.tcp.TCPClient;
27 import org.opendaylight.netconf.transport.tls.TLSClient;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
29 import org.osgi.service.component.annotations.Activate;
30 import org.osgi.service.component.annotations.Component;
31 import org.osgi.service.component.annotations.Deactivate;
32
33 @Singleton
34 @Component(immediate = true, service = NetconfClientFactory.class, property = "type=netconf-client-factory")
35 public class NetconfClientFactoryImpl implements NetconfClientFactory {
36     private final Timer timer = new HashedWheelTimer();
37     private final SSHTransportStackFactory factory;
38
39     @Activate
40     public NetconfClientFactoryImpl() {
41         // TODO make factory component configurable for osgi
42         this(new SSHTransportStackFactory("odl-netconf-client", 0));
43     }
44
45     public NetconfClientFactoryImpl(final SSHTransportStackFactory factory) {
46         this.factory = requireNonNull(factory);
47     }
48
49     @Deactivate
50     @Override
51     public void close() {
52         timer.stop();
53         factory.close();
54     }
55
56     @Override
57     public ListenableFuture<NetconfClientSession> createClient(final NetconfClientConfiguration configuration)
58             throws UnsupportedConfigurationException {
59         final var protocol = configuration.getProtocol();
60         final var future = SettableFuture.<NetconfClientSession>create();
61         final var channelInitializer = new ClientChannelInitializer(createNegotiatorFactory(configuration),
62             () -> configuration.getSessionListener());
63         final var bootstrap = factory.newBootstrap();
64
65         if (TCP.equals(protocol)) {
66             TCPClient.connect(new ClientTransportChannelListener(future, channelInitializer), bootstrap,
67                 configuration.getTcpParameters());
68         } else if (TLS.equals(protocol)) {
69             if (configuration.getTlsParameters() != null) {
70                 TLSClient.connect(new ClientTransportChannelListener(future, channelInitializer), bootstrap,
71                     configuration.getTcpParameters(), configuration.getTlsParameters());
72             } else {
73                 TLSClient.connect(new ClientTransportChannelListener(future, channelInitializer), bootstrap,
74                     configuration.getTcpParameters(), configuration.getTransportSslHandlerFactory());
75             }
76         } else if (SSH.equals(protocol)) {
77             factory.connectClient("netconf", new ClientTransportChannelListener(future, channelInitializer),
78                 configuration.getTcpParameters(), configuration.getSshParameters());
79         }
80         return future;
81     }
82
83     private NetconfClientSessionNegotiatorFactory createNegotiatorFactory(
84             final NetconfClientConfiguration configuration) {
85         final var capabilities = configuration.getOdlHelloCapabilities();
86         if (capabilities == null || capabilities.isEmpty()) {
87             return new NetconfClientSessionNegotiatorFactory(timer, configuration.getAdditionalHeader(),
88                 configuration.getConnectionTimeoutMillis(), configuration.getMaximumIncomingChunkSize());
89         }
90         final var stringCapabilities = capabilities.stream().map(Uri::getValue)
91             .collect(ImmutableSet.toImmutableSet());
92         return new NetconfClientSessionNegotiatorFactory(timer, configuration.getAdditionalHeader(),
93             configuration.getConnectionTimeoutMillis(), stringCapabilities);
94     }
95
96     private record ClientTransportChannelListener(
97             SettableFuture<NetconfClientSession> future,
98             ClientChannelInitializer initializer) implements TransportChannelListener {
99         ClientTransportChannelListener {
100             requireNonNull(future);
101             requireNonNull(initializer);
102         }
103
104         @Override
105         public void onTransportChannelEstablished(final TransportChannel channel) {
106             final var nettyChannel = channel.channel();
107             final var promise = nettyChannel.eventLoop().<NetconfClientSession>newPromise();
108             initializer.initialize(nettyChannel, promise);
109             promise.addListener(ignored -> {
110                 final var cause = promise.cause();
111                 if (cause != null) {
112                     future.setException(cause);
113                 } else {
114                     future.set(promise.getNow());
115                 }
116             });
117         }
118
119         @Override
120         public void onTransportChannelFailed(final Throwable cause) {
121             future.setException(cause);
122         }
123     }
124 }