2 * Copyright (c) 2023 PANTHEON.tech s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.netconf.callhome.mount;
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.util.concurrent.ListenableFuture;
12 import com.google.common.util.concurrent.SettableFuture;
13 import io.netty.channel.Channel;
14 import io.netty.util.Timer;
15 import java.net.InetSocketAddress;
16 import java.net.SocketAddress;
18 import java.util.concurrent.ConcurrentHashMap;
19 import java.util.concurrent.Executor;
20 import javax.annotation.PreDestroy;
21 import javax.inject.Inject;
22 import javax.inject.Singleton;
23 import org.opendaylight.controller.config.threadpool.ThreadPool;
24 import org.opendaylight.mdsal.binding.api.DataBroker;
25 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
26 import org.opendaylight.netconf.callhome.server.CallHomeStatusRecorder;
27 import org.opendaylight.netconf.callhome.server.ssh.CallHomeSshSessionContext;
28 import org.opendaylight.netconf.callhome.server.ssh.CallHomeSshSessionContextManager;
29 import org.opendaylight.netconf.callhome.server.tls.CallHomeTlsAuthProvider;
30 import org.opendaylight.netconf.callhome.server.tls.CallHomeTlsSessionContext;
31 import org.opendaylight.netconf.callhome.server.tls.CallHomeTlsSessionContextManager;
32 import org.opendaylight.netconf.client.NetconfClientFactory;
33 import org.opendaylight.netconf.client.NetconfClientSession;
34 import org.opendaylight.netconf.client.NetconfClientSessionListener;
35 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
36 import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
37 import org.opendaylight.netconf.client.mdsal.api.BaseNetconfSchemas;
38 import org.opendaylight.netconf.client.mdsal.api.DeviceActionFactory;
39 import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
40 import org.opendaylight.netconf.shaded.sshd.client.session.ClientSession;
41 import org.opendaylight.netconf.topology.spi.NetconfClientConfigurationBuilderFactory;
42 import org.opendaylight.netconf.topology.spi.NetconfNodeHandler;
43 import org.opendaylight.netconf.topology.spi.NetconfNodeUtils;
44 import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.TcpClientParametersBuilder;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231121.connection.parameters.Protocol;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231121.connection.parameters.ProtocolBuilder;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev231121.NetconfNodeBuilder;
52 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
53 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
54 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
55 import org.opendaylight.yangtools.yang.common.Decimal64;
56 import org.opendaylight.yangtools.yang.common.Uint16;
57 import org.opendaylight.yangtools.yang.common.Uint32;
58 import org.osgi.service.component.annotations.Activate;
59 import org.osgi.service.component.annotations.Component;
60 import org.osgi.service.component.annotations.Deactivate;
61 import org.osgi.service.component.annotations.Reference;
64 * Service is responsible for call-home to topology integration.
67 * To manage remote device as a topology node the topology component (based on
68 * {@link org.opendaylight.netconf.topology.spi.AbstractNetconfTopology AbstractNetconfTopology}) creates an instance
69 * of {@link org.opendaylight.netconf.topology.spi.NetconfNodeHandler NetconfNodeHandler} based on provided
73 * The mentioned NetconfNodeHandler initializes connection to remote device via sequence of following actions (see
74 * {@link org.opendaylight.netconf.topology.spi.AbstractNetconfTopology#ensureNode(Node) ensureNode(Node)} and
75 * {@link NetconfNodeHandler#lockedConnect() connect()}):
78 * <li>Builds an instance of {@link org.opendaylight.netconf.client.mdsal.NetconfDeviceCommunicator
79 * NetconfDeviceCommunicator} implementation of {@link NetconfClientSessionListener} which is used to check the
80 * NETCONF session state and communicate with device using NETCONF protocol </li>
81 * <li>Builds Netconf client configuration using provided {@link NetconfClientConfigurationBuilderFactory}</li>
82 * <li>Builds Netconf client using configuration composed and triggers connection</li>
86 * This service uses custom implementations of {@link NetconfClientConfigurationBuilderFactory} and
87 * {@link NetconfClientFactory} in order to capture the instance of {@link NetconfClientSessionListener} from topology
88 * component which is required to establish NETCONF layer. See {@link #createClientConfigurationBuilderFactory()}
89 * and {@link #createClientFactory()}.
92 * Following sequence of actions is performed when incoming connection is mapped to topology node:
95 * <li>When incoming connection is identified the {@link CallHomeSshSessionContext} instance expected to be created.
96 * The createContext() method is invoked within protocol associated
97 * {@link org.opendaylight.netconf.callhome.server.CallHomeSessionContextManager CallHomeSessionContextManager} --
98 * see {@link #createSshSessionContextManager()} and
99 * {@link #createTlsSessionContextManager(CallHomeTlsAuthProvider, CallHomeStatusRecorder)}</li>
100 * <li>Due to both {@link NetconfClientSessionListener} and {@link SettableFuture} are required to build session
101 * context the {@link CallHomeTopology#enableNode(Node)} (Node)} is called using synthetic {@link Node} instance
102 * composed via {@link #asNode(String, SocketAddress, Protocol)}. This triggers Netconf client construct/connect
103 * logic (as explained above) resulting captured object placed into {@link #netconfLayerMapping}.</li>
104 * <li>Accepted instance of {@link NetconfClientSessionListener} is used to establish Netconf layer --
105 * see {@link org.opendaylight.netconf.callhome.server.CallHomeTransportChannelListener
106 * CallHomeTransportChannelListener} </li>
107 * <li>Accepted instance of {@link SettableFuture} (representing connection to remote device) is used to
108 * signal connection state to topology component</li>
111 @Component(service = CallHomeMountService.class, immediate = true)
113 public final class CallHomeMountService implements AutoCloseable {
114 private static final Protocol SSH_PROTOCOL = new ProtocolBuilder().setName(Protocol.Name.SSH).build();
115 private static final Protocol TLS_PROTOCOL = new ProtocolBuilder().setName(Protocol.Name.TLS).build();
117 private final Map<String, NetconfLayer> netconfLayerMapping = new ConcurrentHashMap<>();
118 private final CallHomeTopology topology;
122 public CallHomeMountService(
123 final @Reference(target = "(type=global-timer)") Timer timer,
124 final @Reference(target = "(type=global-netconf-processing-executor)") ThreadPool processingThreadPool,
125 final @Reference SchemaResourceManager schemaRepositoryProvider,
126 final @Reference BaseNetconfSchemas baseSchemas,
127 final @Reference DataBroker dataBroker,
128 final @Reference DOMMountPointService mountService,
129 final @Reference DeviceActionFactory deviceActionFactory) {
130 this(NetconfNodeUtils.DEFAULT_TOPOLOGY_NAME, timer,
131 processingThreadPool.getExecutor(), schemaRepositoryProvider, baseSchemas,
132 dataBroker, mountService, deviceActionFactory);
135 public CallHomeMountService(final String topologyId, final Timer timer, final Executor executor,
136 final SchemaResourceManager schemaRepositoryProvider, final BaseNetconfSchemas baseSchemas,
137 final DataBroker dataBroker, final DOMMountPointService mountService,
138 final DeviceActionFactory deviceActionFactory) {
140 final var clientConfBuilderFactory = createClientConfigurationBuilderFactory();
141 final var clientFactory = createClientFactory();
142 topology = new CallHomeTopology(topologyId, clientFactory, timer, executor,
143 schemaRepositoryProvider, dataBroker, mountService, clientConfBuilderFactory,
144 baseSchemas, deviceActionFactory);
148 CallHomeMountService(final CallHomeTopology topology) {
149 this.topology = topology;
153 static NetconfClientConfigurationBuilderFactory createClientConfigurationBuilderFactory() {
154 // use minimal configuration, only id and session listener are used
155 return (nodeId, node) -> NetconfClientConfigurationBuilder.create()
156 .withName(nodeId.getValue())
157 // below parameters are only required to pass configuration validation
158 // actual values play no role
159 .withProtocol(NetconfClientConfiguration.NetconfClientProtocol.TCP)
160 .withTcpParameters(new TcpClientParametersBuilder().build());
164 NetconfClientFactory createClientFactory() {
165 return new NetconfClientFactory() {
167 public ListenableFuture<NetconfClientSession> createClient(
168 final NetconfClientConfiguration clientConfiguration) throws UnsupportedConfigurationException {
169 final var future = SettableFuture.<NetconfClientSession>create();
170 final var pending = new NetconfLayer(clientConfiguration.getName(),
171 clientConfiguration.getSessionListener(), future);
172 netconfLayerMapping.put(pending.id, pending);
177 public void close() throws Exception {
183 private static Node asNode(final String id, final SocketAddress socketAddress, final Protocol protocol) {
184 final var nodeAddress = socketAddress instanceof InetSocketAddress inetSocketAddress
185 ? inetSocketAddress : new InetSocketAddress("0.0.0.0", 0);
186 // construct synthetic Node object with minimal required parameters
187 return new NodeBuilder()
188 .setNodeId(new NodeId(id))
189 .addAugmentation(new NetconfNodeBuilder()
190 .setHost(new Host(IetfInetUtil.ipAddressFor(nodeAddress.getAddress())))
191 .setPort(new PortNumber(Uint16.valueOf(nodeAddress.getPort())))
193 .setProtocol(protocol)
194 // below parameters are required for NetconfNodeHandler
195 .setSchemaless(false)
196 .setReconnectOnChangedSchema(false)
197 .setConnectionTimeoutMillis(Uint32.valueOf(20000))
198 .setDefaultRequestTimeoutMillis(Uint32.valueOf(60000))
199 .setMaxConnectionAttempts(Uint32.ZERO)
200 .setBetweenAttemptsTimeoutMillis(Uint16.valueOf(2000))
201 .setSleepFactor(Decimal64.valueOf("1.5"))
202 .setKeepaliveDelay(Uint32.valueOf(120))
203 .setConcurrentRpcLimit(Uint16.ZERO)
204 .setActorResponseWaitTime(Uint16.valueOf(5))
205 .setLockDatastore(true)
210 public CallHomeSshSessionContextManager createSshSessionContextManager() {
211 return new CallHomeSshSessionContextManager() {
213 public CallHomeSshSessionContext createContext(final String id, final ClientSession clientSession) {
214 topology.enableNode(asNode(id, clientSession.getRemoteAddress(), SSH_PROTOCOL));
215 final var netconfLayer = netconfLayerMapping.remove(id);
216 return netconfLayer == null ? null : new CallHomeSshSessionContext(id, clientSession.getRemoteAddress(),
217 clientSession, netconfLayer.sessionListener, netconfLayer.netconfSessionFuture);
221 public void remove(String id) {
223 topology.disableNode(new NodeId(id));
228 public CallHomeTlsSessionContextManager createTlsSessionContextManager(final CallHomeTlsAuthProvider authProvider,
229 final CallHomeStatusRecorder statusRecorder) {
230 return new CallHomeTlsSessionContextManager(authProvider, statusRecorder) {
232 public CallHomeTlsSessionContext createContext(final String id, final Channel channel) {
233 topology.enableNode(asNode(id, channel.remoteAddress(), TLS_PROTOCOL));
234 final var netconfLayer = netconfLayerMapping.remove(id);
235 return netconfLayer == null ? null : new CallHomeTlsSessionContext(id, channel,
236 netconfLayer.sessionListener, netconfLayer.netconfSessionFuture());
240 public void remove(String id) {
242 topology.disableNode(new NodeId(id));
250 public void close() {
251 netconfLayerMapping.forEach((key, value) -> value.netconfSessionFuture.cancel(true));
252 netconfLayerMapping.clear();
255 private record NetconfLayer(String id, NetconfClientSessionListener sessionListener,
256 SettableFuture<NetconfClientSession> netconfSessionFuture) {