Skip to content

Commit 02ab04f

Browse files
committed
CAMEL-23000: camel-ssh - idleTimeout
1 parent 77f117a commit 02ab04f

File tree

13 files changed

+518
-102
lines changed

13 files changed

+518
-102
lines changed

catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/ssh.json

Lines changed: 42 additions & 40 deletions
Large diffs are not rendered by default.

components/camel-ssh/src/generated/java/org/apache/camel/component/ssh/SshComponentConfigurer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj
5151
case "healthCheckConsumerEnabled": target.setHealthCheckConsumerEnabled(property(camelContext, boolean.class, value)); return true;
5252
case "healthcheckproducerenabled":
5353
case "healthCheckProducerEnabled": target.setHealthCheckProducerEnabled(property(camelContext, boolean.class, value)); return true;
54+
case "idletimeout":
55+
case "idleTimeout": getOrCreateConfiguration(target).setIdleTimeout(property(camelContext, long.class, value)); return true;
5456
case "kex": getOrCreateConfiguration(target).setKex(property(camelContext, java.lang.String.class, value)); return true;
5557
case "keypairprovider":
5658
case "keyPairProvider": getOrCreateConfiguration(target).setKeyPairProvider(property(camelContext, org.apache.sshd.common.keyprovider.KeyPairProvider.class, value)); return true;
@@ -104,6 +106,8 @@ public Class<?> getOptionType(String name, boolean ignoreCase) {
104106
case "healthCheckConsumerEnabled": return boolean.class;
105107
case "healthcheckproducerenabled":
106108
case "healthCheckProducerEnabled": return boolean.class;
109+
case "idletimeout":
110+
case "idleTimeout": return long.class;
107111
case "kex": return java.lang.String.class;
108112
case "keypairprovider":
109113
case "keyPairProvider": return org.apache.sshd.common.keyprovider.KeyPairProvider.class;
@@ -153,6 +157,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
153157
case "healthCheckConsumerEnabled": return target.isHealthCheckConsumerEnabled();
154158
case "healthcheckproducerenabled":
155159
case "healthCheckProducerEnabled": return target.isHealthCheckProducerEnabled();
160+
case "idletimeout":
161+
case "idleTimeout": return getOrCreateConfiguration(target).getIdleTimeout();
156162
case "kex": return getOrCreateConfiguration(target).getKex();
157163
case "keypairprovider":
158164
case "keyPairProvider": return getOrCreateConfiguration(target).getKeyPairProvider();

components/camel-ssh/src/generated/java/org/apache/camel/component/ssh/SshEndpointConfigurer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj
4949
case "failonunknownhost":
5050
case "failOnUnknownHost": target.getConfiguration().setFailOnUnknownHost(property(camelContext, boolean.class, value)); return true;
5151
case "greedy": target.setGreedy(property(camelContext, boolean.class, value)); return true;
52+
case "idletimeout":
53+
case "idleTimeout": target.getConfiguration().setIdleTimeout(property(camelContext, long.class, value)); return true;
5254
case "initialdelay":
5355
case "initialDelay": target.setInitialDelay(property(camelContext, long.class, value)); return true;
5456
case "kex": target.getConfiguration().setKex(property(camelContext, java.lang.String.class, value)); return true;
@@ -128,6 +130,8 @@ public Class<?> getOptionType(String name, boolean ignoreCase) {
128130
case "failonunknownhost":
129131
case "failOnUnknownHost": return boolean.class;
130132
case "greedy": return boolean.class;
133+
case "idletimeout":
134+
case "idleTimeout": return long.class;
131135
case "initialdelay":
132136
case "initialDelay": return long.class;
133137
case "kex": return java.lang.String.class;
@@ -203,6 +207,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
203207
case "failonunknownhost":
204208
case "failOnUnknownHost": return target.getConfiguration().isFailOnUnknownHost();
205209
case "greedy": return target.isGreedy();
210+
case "idletimeout":
211+
case "idleTimeout": return target.getConfiguration().getIdleTimeout();
206212
case "initialdelay":
207213
case "initialDelay": return target.getInitialDelay();
208214
case "kex": return target.getConfiguration().getKex();

components/camel-ssh/src/generated/java/org/apache/camel/component/ssh/SshEndpointUriFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class SshEndpointUriFactory extends org.apache.camel.support.component.En
2323
private static final Set<String> SECRET_PROPERTY_NAMES;
2424
private static final Map<String, String> MULTI_VALUE_PREFIXES;
2525
static {
26-
Set<String> props = new HashSet<>(41);
26+
Set<String> props = new HashSet<>(42);
2727
props.add("backoffErrorThreshold");
2828
props.add("backoffIdleThreshold");
2929
props.add("backoffMultiplier");
@@ -40,6 +40,7 @@ public class SshEndpointUriFactory extends org.apache.camel.support.component.En
4040
props.add("failOnUnknownHost");
4141
props.add("greedy");
4242
props.add("host");
43+
props.add("idleTimeout");
4344
props.add("initialDelay");
4445
props.add("kex");
4546
props.add("keyPairProvider");

components/camel-ssh/src/generated/resources/META-INF/org/apache/camel/component/ssh/ssh.json

Lines changed: 42 additions & 40 deletions
Large diffs are not rendered by default.

components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public class SshConfiguration implements Cloneable {
7474
private String signatures;
7575
@UriParam(label = "advanced")
7676
private String compressions;
77+
@UriParam(label = "advanced")
78+
private long idleTimeout;
7779
@UriParam
7880
@Metadata(label = "advanced", autowired = true)
7981
private ClientBuilder clientBuilder;
@@ -396,6 +398,20 @@ public void setCompressions(String compressions) {
396398
this.compressions = compressions;
397399
}
398400

401+
public long getIdleTimeout() {
402+
return idleTimeout;
403+
}
404+
405+
/**
406+
* Sets the timeout in milliseconds to wait before the SSH session is closed due to inactivity. The default value is
407+
* 0, which means no idle timeout is applied.
408+
*
409+
* @param idleTimeout long milliseconds to wait before the session is closed due to inactivity.
410+
*/
411+
public void setIdleTimeout(long idleTimeout) {
412+
this.idleTimeout = idleTimeout;
413+
}
414+
399415
public ClientBuilder getClientBuilder() {
400416
return clientBuilder;
401417
}

components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConsumer.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import org.apache.camel.support.ScheduledPollConsumer;
2222
import org.apache.sshd.client.SshClient;
2323

24-
import static org.apache.camel.component.ssh.SshUtils.*;
25-
2624
public class SshConsumer extends ScheduledPollConsumer {
2725
private final SshEndpoint endpoint;
2826

@@ -35,15 +33,7 @@ public SshConsumer(SshEndpoint endpoint, Processor processor) {
3533

3634
@Override
3735
protected void doStart() throws Exception {
38-
if (this.endpoint.getConfiguration() == null || this.endpoint.getConfiguration().getClientBuilder() == null) {
39-
client = SshClient.setUpDefaultClient();
40-
} else {
41-
client = this.endpoint.getConfiguration().getClientBuilder().build(true);
42-
}
43-
SshConfiguration configuration = endpoint.getConfiguration();
44-
configureAlgorithms(configuration, client);
45-
46-
client.start();
36+
client = SshUtils.createAndStartClient(endpoint.getConfiguration());
4737

4838
super.doStart();
4939
}

components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshProducer.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import org.apache.camel.support.DefaultProducer;
2525
import org.apache.sshd.client.SshClient;
2626

27-
import static org.apache.camel.component.ssh.SshUtils.*;
28-
2927
public class SshProducer extends DefaultProducer {
3028
private SshEndpoint endpoint;
3129
private SshClient client;
@@ -37,14 +35,7 @@ public SshProducer(SshEndpoint endpoint) {
3735

3836
@Override
3937
protected void doStart() throws Exception {
40-
if (this.endpoint.getConfiguration() == null || this.endpoint.getConfiguration().getClientBuilder() == null) {
41-
client = SshClient.setUpDefaultClient();
42-
} else {
43-
client = this.endpoint.getConfiguration().getClientBuilder().build(true);
44-
}
45-
SshConfiguration configuration = endpoint.getConfiguration();
46-
configureAlgorithms(configuration, client);
47-
client.start();
38+
client = SshUtils.createAndStartClient(endpoint.getConfiguration());
4839

4940
super.doStart();
5041
}

components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshUtils.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.apache.camel.component.ssh;
1818

19+
import java.time.Duration;
1920
import java.util.ArrayList;
2021
import java.util.Collection;
2122
import java.util.HashSet;
@@ -43,6 +44,7 @@
4344
import org.apache.sshd.common.signature.BuiltinSignatures;
4445
import org.apache.sshd.common.signature.Signature;
4546
import org.apache.sshd.common.signature.SignatureFactory;
47+
import org.apache.sshd.core.CoreModuleProperties;
4648
import org.slf4j.Logger;
4749
import org.slf4j.LoggerFactory;
4850

@@ -168,4 +170,19 @@ public static void configureAlgorithms(SshConfiguration configuration, SshClient
168170
configureCompressions(configuration.getCompressions(), client);
169171
}
170172

173+
public static SshClient createAndStartClient(SshConfiguration configuration) throws Exception {
174+
SshClient client;
175+
if (configuration == null || configuration.getClientBuilder() == null) {
176+
client = SshClient.setUpDefaultClient();
177+
} else {
178+
client = configuration.getClientBuilder().build(true);
179+
}
180+
configureAlgorithms(configuration, client);
181+
if (configuration.getIdleTimeout() > 0) {
182+
CoreModuleProperties.IDLE_TIMEOUT.set(client, Duration.ofMillis(configuration.getIdleTimeout()));
183+
}
184+
client.start();
185+
return client;
186+
}
187+
171188
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.camel.component.ssh;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.OutputStream;
22+
23+
import org.apache.sshd.server.Environment;
24+
import org.apache.sshd.server.ExitCallback;
25+
import org.apache.sshd.server.channel.ChannelSession;
26+
import org.apache.sshd.server.command.Command;
27+
import org.apache.sshd.server.command.CommandFactory;
28+
29+
/**
30+
* A command factory that introduces a delay before echoing the command back. Used to test idle timeout behavior — if
31+
* the client's idle timeout is shorter than the delay, the session will be closed before the command completes.
32+
*/
33+
public class DelayedEchoCommandFactory implements CommandFactory {
34+
35+
private final long delayMs;
36+
37+
public DelayedEchoCommandFactory(long delayMs) {
38+
this.delayMs = delayMs;
39+
}
40+
41+
@Override
42+
public Command createCommand(ChannelSession channelSession, String command) {
43+
return new DelayedEchoCommand(command, delayMs);
44+
}
45+
46+
protected static class DelayedEchoCommand implements Command, Runnable {
47+
private final String command;
48+
private final long delayMs;
49+
private OutputStream out;
50+
private OutputStream err;
51+
private ExitCallback callback;
52+
private Thread thread;
53+
54+
public DelayedEchoCommand(String command, long delayMs) {
55+
this.command = command;
56+
this.delayMs = delayMs;
57+
}
58+
59+
@Override
60+
public void setInputStream(InputStream in) {
61+
}
62+
63+
@Override
64+
public void setOutputStream(OutputStream out) {
65+
this.out = out;
66+
}
67+
68+
@Override
69+
public void setErrorStream(OutputStream err) {
70+
this.err = err;
71+
}
72+
73+
@Override
74+
public void setExitCallback(ExitCallback callback) {
75+
this.callback = callback;
76+
}
77+
78+
@Override
79+
public void start(ChannelSession channelSession, Environment environment) throws IOException {
80+
thread = new Thread(this, "DelayedEchoCommand");
81+
thread.start();
82+
}
83+
84+
@Override
85+
public void destroy(ChannelSession channelSession) throws Exception {
86+
// noop
87+
}
88+
89+
@Override
90+
public void run() {
91+
boolean succeeded = true;
92+
String message = null;
93+
try {
94+
Thread.sleep(delayMs);
95+
err.write("Error:".getBytes());
96+
err.write(command.getBytes());
97+
err.flush();
98+
out.write(command.getBytes());
99+
out.flush();
100+
} catch (Exception e) {
101+
succeeded = false;
102+
message = e.toString();
103+
} finally {
104+
if (succeeded) {
105+
callback.onExit(0);
106+
} else {
107+
callback.onExit(1, message);
108+
}
109+
}
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)