0ad6ae59fe189e9269ca93fc318f2880e8af9b37
[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 javax.annotation.PreDestroy;
19 import javax.inject.Inject;
20 import javax.inject.Singleton;
21 import org.opendaylight.netconf.api.TransportConstants;
22 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
23 import org.opendaylight.netconf.common.NetconfTimer;
24 import org.opendaylight.netconf.transport.api.TransportChannel;
25 import org.opendaylight.netconf.transport.api.TransportChannelListener;
26 import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
27 import org.opendaylight.netconf.transport.ssh.SSHTransportStackFactory;
28 import org.opendaylight.netconf.transport.tcp.TCPClient;
29 import org.opendaylight.netconf.transport.tls.FixedSslHandlerFactory;
30 import org.opendaylight.netconf.transport.tls.TLSClient;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
32 import org.osgi.service.component.annotations.Activate;
33 import org.osgi.service.component.annotations.Component;
34 import org.osgi.service.component.annotations.Deactivate;
35 import org.osgi.service.component.annotations.Reference;
36
37 @Singleton
38 @Component(service = NetconfClientFactory.class)
39 public final class NetconfClientFactoryImpl implements NetconfClientFactory {
40     private final SSHTransportStackFactory factory;
41     private final NetconfTimer timer;
42
43     public NetconfClientFactoryImpl(final NetconfTimer timer, final SSHTransportStackFactory factory) {
44         this.timer = requireNonNull(timer);
45         this.factory = requireNonNull(factory);
46     }
47
48     @Inject
49     @Activate
50     public NetconfClientFactoryImpl(@Reference final NetconfTimer timer) {
51         // FIXME: make factory component configurable for OSGi
52         this(timer, new SSHTransportStackFactory("odl-netconf-client", 0));
53     }
54
55     @PreDestroy
56     @Deactivate
57     @Override
58     public void close() {
59         factory.close();
60     }
61
62     @Override
63     public ListenableFuture<NetconfClientSession> createClient(final NetconfClientConfiguration configuration)
64             throws UnsupportedConfigurationException {
65         final var protocol = configuration.getProtocol();
66         final var future = SettableFuture.<NetconfClientSession>create();
67         final var channelInitializer = new ClientChannelInitializer(createNegotiatorFactory(configuration),
68             () -> configuration.getSessionListener());
69         final var bootstrap = factory.newBootstrap();
70
71         if (TCP.equals(protocol)) {
72             TCPClient.connect(new ClientTransportChannelListener(future, channelInitializer), bootstrap,
73                 configuration.getTcpParameters());
74         } else if (TLS.equals(protocol)) {
75             if (configuration.getTlsParameters() != null) {
76                 TLSClient.connect(new ClientTransportChannelListener(future, channelInitializer), bootstrap,
77                     configuration.getTcpParameters(), new FixedSslHandlerFactory(configuration.getTlsParameters()));
78             } else {
79                 TLSClient.connect(new ClientTransportChannelListener(future, channelInitializer), bootstrap,
80                     configuration.getTcpParameters(), configuration.getSslHandlerFactory());
81             }
82         } else if (SSH.equals(protocol)) {
83             factory.connectClient(TransportConstants.SSH_SUBSYSTEM,
84                 new ClientTransportChannelListener(future, channelInitializer), configuration.getTcpParameters(),
85                 configuration.getSshParameters(), configuration.getSshConfigurator());
86         }
87         return future;
88     }
89
90     private NetconfClientSessionNegotiatorFactory createNegotiatorFactory(
91             final NetconfClientConfiguration configuration) {
92         final var capabilities = configuration.getOdlHelloCapabilities();
93         if (capabilities == null || capabilities.isEmpty()) {
94             return new NetconfClientSessionNegotiatorFactory(timer, configuration.getAdditionalHeader(),
95                 configuration.getConnectionTimeoutMillis(), configuration.getMaximumIncomingChunkSize());
96         }
97         final var stringCapabilities = capabilities.stream().map(Uri::getValue)
98             .collect(ImmutableSet.toImmutableSet());
99         return new NetconfClientSessionNegotiatorFactory(timer, configuration.getAdditionalHeader(),
100             configuration.getConnectionTimeoutMillis(), stringCapabilities);
101     }
102
103     private record ClientTransportChannelListener(
104             SettableFuture<NetconfClientSession> future,
105             ClientChannelInitializer initializer) implements TransportChannelListener {
106         ClientTransportChannelListener {
107             requireNonNull(future);
108             requireNonNull(initializer);
109         }
110
111         @Override
112         public void onTransportChannelEstablished(final TransportChannel channel) {
113             final var nettyChannel = channel.channel();
114             final var promise = nettyChannel.eventLoop().<NetconfClientSession>newPromise();
115             initializer.initialize(nettyChannel, promise);
116             promise.addListener(ignored -> {
117                 final var cause = promise.cause();
118                 if (cause != null) {
119                     future.setException(cause);
120                 } else {
121                     future.set(promise.getNow());
122                 }
123             });
124         }
125
126         @Override
127         public void onTransportChannelFailed(final Throwable cause) {
128             future.setException(cause);
129         }
130     }
131 }