25
25
import org .apache .logging .log4j .Logger ;
26
26
import org .junit .jupiter .api .Assertions ;
27
27
28
+ import javax .annotation .Nonnull ;
28
29
import javax .annotation .Nullable ;
29
30
import java .io .File ;
30
31
import java .io .IOException ;
32
+ import java .net .ServerSocket ;
31
33
import java .util .Arrays ;
32
34
import java .util .List ;
33
35
import java .util .Objects ;
@@ -44,11 +46,10 @@ public class ExternalServer {
44
46
public static final String EXTERNAL_SERVER_PROPERTY_NAME = "yaml_testing_external_server" ;
45
47
private static final boolean SAVE_SERVER_OUTPUT = false ;
46
48
47
- @ Nullable
49
+ @ Nonnull
48
50
private final File serverJar ;
49
- private final int grpcPort ;
50
- private final int httpPort ;
51
- private SemanticVersion version ;
51
+ private int grpcPort ;
52
+ private final SemanticVersion version ;
52
53
private Process serverProcess ;
53
54
@ Nullable
54
55
private final String clusterFile ;
@@ -58,21 +59,13 @@ public class ExternalServer {
58
59
*
59
60
* @param serverJar the path to the jar to run
60
61
*/
61
- public ExternalServer (@ Nullable File serverJar , final int grpcPort , final int httpPort ) {
62
- this ( serverJar , grpcPort , httpPort , null );
63
- }
62
+ public ExternalServer (@ Nonnull final File serverJar ,
63
+ @ Nullable final String clusterFile ) throws IOException {
64
+ this . clusterFile = clusterFile ;
64
65
65
- /**
66
- * Create a new instance that will run a specific jar.
67
- *
68
- * @param serverJar the path to the jar to run
69
- */
70
- public ExternalServer (@ Nullable File serverJar , final int grpcPort , final int httpPort ,
71
- @ Nullable final String clusterFile ) {
72
66
this .serverJar = serverJar ;
73
- this .grpcPort = grpcPort ;
74
- this .httpPort = httpPort ;
75
- this .clusterFile = clusterFile ;
67
+ Assertions .assertTrue (this .serverJar .exists (), "Jar could not be found " + serverJar .getAbsolutePath ());
68
+ this .version = getVersion (this .serverJar );
76
69
}
77
70
78
71
static {
@@ -113,20 +106,12 @@ public SemanticVersion getVersion() {
113
106
}
114
107
115
108
public void start () throws Exception {
116
- File jar ;
117
- if (serverJar == null ) {
118
- final List <File > externalServers = getAvailableServers ();
119
- Assertions .assertEquals (1 , externalServers .size ());
120
- jar = externalServers .get (0 );
121
- } else {
122
- jar = serverJar ;
123
- }
124
- Assertions .assertTrue (jar .exists (), "Jar could not be found " + jar .getAbsolutePath ());
125
- this .version = getVersion (jar );
109
+ grpcPort = getAvailablePort (-1 );
110
+ final int httpPort = getAvailablePort (grpcPort );
126
111
ProcessBuilder processBuilder = new ProcessBuilder ("java" ,
127
112
// TODO add support for debugging by adding, but need to take care with ports
128
113
// "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n",
129
- "-jar" , jar .getAbsolutePath (),
114
+ "-jar" , serverJar .getAbsolutePath (),
130
115
"--grpcPort" , Integer .toString (grpcPort ), "--httpPort" , Integer .toString (httpPort ));
131
116
ProcessBuilder .Redirect out = SAVE_SERVER_OUTPUT ?
132
117
ProcessBuilder .Redirect .to (File .createTempFile ("JdbcServerOut-" + grpcPort , ".log" )) :
@@ -144,7 +129,7 @@ public void start() throws Exception {
144
129
Assertions .fail ("Failed to start the external server" );
145
130
}
146
131
147
- logger .info ("Started {} Version: {}" , jar , version );
132
+ logger .info ("Started {} Version: {}" , serverJar , version );
148
133
}
149
134
150
135
/**
@@ -201,6 +186,32 @@ private boolean startServer(ProcessBuilder processBuilder) throws IOException, I
201
186
return serverProcess .isAlive ();
202
187
}
203
188
189
+ /**
190
+ * Get a port that is currently available for the server.
191
+ * @param unavailablePort Get a port that you know will be unavailable. This is mostly useful because the server
192
+ * needs two ports, one for GRPC, and one for HTTP, so the GRPC port can be noted as unavailable when asking for
193
+ * the http port and both can be provided to the server. If nothing is unavailable, use a negative number.
194
+ * @return a port that is not currently in use on the system.
195
+ */
196
+ private int getAvailablePort (final int unavailablePort ) {
197
+ // running locally on my laptop, testing if a port is available takes 0 milliseconds, so no need to optimize
198
+ for (int i = 1111 ; i < 9999 ; i ++) {
199
+ if (i != unavailablePort && isAvailable (i )) {
200
+ return i ;
201
+ }
202
+ }
203
+ return Assertions .fail ("Could not find available port between 1111 and 9999" );
204
+ }
205
+
206
+ public static boolean isAvailable (int port ) {
207
+ try (ServerSocket tcpSocket = new ServerSocket (port )) {
208
+ tcpSocket .setReuseAddress (true );
209
+ return true ;
210
+ } catch (IOException e ) {
211
+ return false ;
212
+ }
213
+ }
214
+
204
215
public void stop () {
205
216
if ((serverProcess != null ) && serverProcess .isAlive ()) {
206
217
serverProcess .destroy ();
0 commit comments