-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix port conflict when running multiple unit tests in parallel #220
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package io.specto.hoverfly.junit.api; | ||
|
||
import io.specto.hoverfly.junit.core.config.HoverflyConfiguration; | ||
|
||
/** | ||
* Factory for creating {@link HoverflyClient}s | ||
*/ | ||
public interface HoverflyClientFactory { | ||
|
||
HoverflyClient createHoverflyClient(HoverflyConfiguration hoverflyConfig); | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package io.specto.hoverfly.junit.api; | ||
|
||
import io.specto.hoverfly.junit.core.HoverflyConstants; | ||
import io.specto.hoverfly.junit.core.config.HoverflyConfiguration; | ||
|
||
public class HoverflyOkHttpClientFactory implements HoverflyClientFactory { | ||
|
||
public HoverflyClient createHoverflyClient(HoverflyConfiguration hoverflyConfig) { | ||
return custom() | ||
.scheme(hoverflyConfig.getScheme()) | ||
.host(hoverflyConfig.getHost()) | ||
.port(hoverflyConfig.getAdminPort()) | ||
.withAuthToken() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
.build(); | ||
} | ||
|
||
/** | ||
* Static factory method for creating a {@link Builder} | ||
* @return a builder for HoverflyClient | ||
*/ | ||
static Builder custom() { | ||
return new Builder(); | ||
} | ||
|
||
/** | ||
* Static factory method for default Hoverfly client | ||
* @return a default HoverflyClient | ||
*/ | ||
static HoverflyClient createDefault() { | ||
return new Builder().build(); | ||
} | ||
|
||
/** | ||
* HTTP client builder for Hoverfly admin API | ||
*/ | ||
static class Builder { | ||
|
||
private String scheme = HoverflyConstants.HTTP; | ||
private String host = HoverflyConstants.LOCALHOST; | ||
private int port = HoverflyConstants.DEFAULT_ADMIN_PORT; | ||
private String authToken = null; | ||
|
||
Builder() { | ||
} | ||
|
||
public Builder scheme(String scheme) { | ||
this.scheme = scheme; | ||
return this; | ||
} | ||
|
||
public Builder host(String host) { | ||
this.host = host; | ||
return this; | ||
} | ||
|
||
public Builder port(int port) { | ||
this.port = port; | ||
return this; | ||
} | ||
|
||
/** | ||
* Get token from environment variable "HOVERFLY_AUTH_TOKEN" to authenticate with admin API | ||
* @return this Builder for further customizations | ||
*/ | ||
public Builder withAuthToken() { | ||
this.authToken = System.getenv(HoverflyConstants.HOVERFLY_AUTH_TOKEN); | ||
return this; | ||
} | ||
|
||
public HoverflyClient build() { | ||
return new OkHttpHoverflyClient(scheme, host, port, authToken); | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
import java.net.ServerSocket; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.time.Duration; | ||
|
@@ -37,6 +38,8 @@ | |
import com.fasterxml.jackson.databind.ObjectWriter; | ||
import io.specto.hoverfly.junit.api.HoverflyClient; | ||
import io.specto.hoverfly.junit.api.HoverflyClientException; | ||
import io.specto.hoverfly.junit.api.HoverflyClientFactory; | ||
import io.specto.hoverfly.junit.api.HoverflyOkHttpClientFactory; | ||
import io.specto.hoverfly.junit.api.model.ModeArguments; | ||
import io.specto.hoverfly.junit.api.view.DiffView; | ||
import io.specto.hoverfly.junit.api.view.HoverflyInfoView; | ||
|
@@ -83,7 +86,8 @@ public class Hoverfly implements AutoCloseable { | |
private final HoverflyMode hoverflyMode; | ||
private final ProxyConfigurer proxyConfigurer; | ||
private final SslConfigurer sslConfigurer = new SslConfigurer(); | ||
private final HoverflyClient hoverflyClient; | ||
private final HoverflyClientFactory hoverflyClientFactory; | ||
private HoverflyClient hoverflyClient; | ||
|
||
private final TempFileManager tempFileManager = new TempFileManager(); | ||
private StartedProcess startedProcess; | ||
|
@@ -100,14 +104,8 @@ public class Hoverfly implements AutoCloseable { | |
public Hoverfly(HoverflyConfig hoverflyConfigBuilder, HoverflyMode hoverflyMode) { | ||
hoverflyConfig = hoverflyConfigBuilder.build(); | ||
this.proxyConfigurer = new ProxyConfigurer(hoverflyConfig); | ||
this.hoverflyClient = HoverflyClient.custom() | ||
.scheme(hoverflyConfig.getScheme()) | ||
.host(hoverflyConfig.getHost()) | ||
.port(hoverflyConfig.getAdminPort()) | ||
.withAuthToken() | ||
.build(); | ||
this.hoverflyMode = hoverflyMode; | ||
|
||
this.hoverflyClientFactory = new HoverflyOkHttpClientFactory(); | ||
} | ||
|
||
/** | ||
|
@@ -139,7 +137,10 @@ public void start() { | |
|
||
if (!hoverflyConfig.isRemoteInstance()) { | ||
startHoverflyProcess(); | ||
} else { | ||
} | ||
this.hoverflyClient = hoverflyClientFactory.createHoverflyClient(hoverflyConfig); | ||
|
||
if (hoverflyConfig.isRemoteInstance()) { | ||
resetJournal(); | ||
} | ||
|
||
|
@@ -152,7 +153,7 @@ public void start() { | |
} | ||
|
||
if (hoverflyConfig.getProxyCaCertificate().isPresent()) { | ||
sslConfigurer.setDefaultSslContext(hoverflyConfig.getProxyCaCertificate().get()); | ||
sslConfigurer.setDefaultSslContext(hoverflyConfig.getProxyCaCertificate().get()); | ||
} else if (StringUtils.isNotBlank(hoverflyConfig.getSslCertificatePath())) { | ||
sslConfigurer.setDefaultSslContext(hoverflyConfig.getSslCertificatePath()); | ||
} else { | ||
|
@@ -163,81 +164,90 @@ public void start() { | |
} | ||
|
||
private void startHoverflyProcess() { | ||
checkPortInUse(hoverflyConfig.getProxyPort()); | ||
checkPortInUse(hoverflyConfig.getAdminPort()); | ||
synchronized (Hoverfly.class) { | ||
|
||
final SystemConfig systemConfig = new SystemConfigFactory(hoverflyConfig).createSystemConfig(); | ||
if (hoverflyConfig.getProxyPort() == 0) { | ||
hoverflyConfig.setProxyPort(findUnusedPort()); | ||
} | ||
if (hoverflyConfig.getAdminPort() == 0) { | ||
hoverflyConfig.setAdminPort(findUnusedPort()); | ||
} | ||
checkPortInUse(hoverflyConfig.getProxyPort()); | ||
checkPortInUse(hoverflyConfig.getAdminPort()); | ||
Comment on lines
+169
to
+176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should move any port checking and setting right before the start of hoverfly, to avoid synchronizing the whole method, only synchronizing the following is what you wanted (in pseudocode):
|
||
|
||
if (hoverflyConfig.getBinaryLocation() != null) { | ||
tempFileManager.setBinaryLocation(hoverflyConfig.getBinaryLocation()); | ||
} | ||
Path binaryPath = tempFileManager.copyHoverflyBinary(systemConfig); | ||
final SystemConfig systemConfig = new SystemConfigFactory(hoverflyConfig).createSystemConfig(); | ||
|
||
LOGGER.info("Executing binary at {}", binaryPath); | ||
final List<String> commands = new ArrayList<>(); | ||
commands.add(binaryPath.toString()); | ||
if (hoverflyConfig.getBinaryLocation() != null) { | ||
tempFileManager.setBinaryLocation(hoverflyConfig.getBinaryLocation()); | ||
} | ||
Path binaryPath = tempFileManager.copyHoverflyBinary(systemConfig); | ||
|
||
if (!hoverflyConfig.getCommands().isEmpty()) { | ||
commands.addAll(hoverflyConfig.getCommands()); | ||
} | ||
commands.add("-pp"); | ||
commands.add(String.valueOf(hoverflyConfig.getProxyPort())); | ||
commands.add("-ap"); | ||
commands.add(String.valueOf(hoverflyConfig.getAdminPort())); | ||
|
||
if (StringUtils.isNotBlank(hoverflyConfig.getSslCertificatePath())) { | ||
tempFileManager.copyClassPathResource(hoverflyConfig.getSslCertificatePath(), "ca.crt"); | ||
commands.add("-cert"); | ||
commands.add("ca.crt"); | ||
} | ||
if (StringUtils.isNotBlank(hoverflyConfig.getSslKeyPath())) { | ||
tempFileManager.copyClassPathResource(hoverflyConfig.getSslKeyPath(), "ca.key"); | ||
commands.add("-key"); | ||
commands.add("ca.key"); | ||
} | ||
if (hoverflyConfig.isPlainHttpTunneling()) { | ||
commands.add("-plain-http-tunneling"); | ||
} | ||
LOGGER.info("Executing binary at {}", binaryPath); | ||
final List<String> commands = new ArrayList<>(); | ||
commands.add(binaryPath.toString()); | ||
|
||
if (hoverflyConfig.isWebServer()) { | ||
commands.add("-webserver"); | ||
} | ||
if (!hoverflyConfig.getCommands().isEmpty()) { | ||
commands.addAll(hoverflyConfig.getCommands()); | ||
} | ||
commands.add("-pp"); | ||
commands.add(String.valueOf(hoverflyConfig.getProxyPort())); | ||
commands.add("-ap"); | ||
commands.add(String.valueOf(hoverflyConfig.getAdminPort())); | ||
|
||
if (StringUtils.isNotBlank(hoverflyConfig.getSslCertificatePath())) { | ||
tempFileManager.copyClassPathResource(hoverflyConfig.getSslCertificatePath(), "ca.crt"); | ||
commands.add("-cert"); | ||
commands.add("ca.crt"); | ||
} | ||
if (StringUtils.isNotBlank(hoverflyConfig.getSslKeyPath())) { | ||
tempFileManager.copyClassPathResource(hoverflyConfig.getSslKeyPath(), "ca.key"); | ||
commands.add("-key"); | ||
commands.add("ca.key"); | ||
} | ||
if (hoverflyConfig.isPlainHttpTunneling()) { | ||
commands.add("-plain-http-tunneling"); | ||
} | ||
|
||
if (hoverflyConfig.isTlsVerificationDisabled()) { | ||
commands.add("-tls-verification=false"); | ||
} | ||
if (hoverflyConfig.isWebServer()) { | ||
commands.add("-webserver"); | ||
} | ||
|
||
if (hoverflyConfig.getHoverflyLogger().isPresent()) { | ||
commands.add("-logs"); | ||
commands.add("json"); | ||
} | ||
if (hoverflyConfig.isTlsVerificationDisabled()) { | ||
commands.add("-tls-verification=false"); | ||
} | ||
|
||
if (hoverflyConfig.getLogLevel().isPresent()) { | ||
commands.add("-log-level"); | ||
commands.add(hoverflyConfig.getLogLevel().get().name().toLowerCase()); | ||
} | ||
if (hoverflyConfig.getHoverflyLogger().isPresent()) { | ||
commands.add("-logs"); | ||
commands.add("json"); | ||
} | ||
|
||
if (hoverflyConfig.isMiddlewareEnabled()) { | ||
final String path = hoverflyConfig.getLocalMiddleware().getPath(); | ||
final String scriptName = path.contains(File.separator) ? path.substring(path.lastIndexOf(File.separator) + 1) : path; | ||
tempFileManager.copyClassPathResource(path, scriptName); | ||
commands.add("-middleware"); | ||
commands.add(hoverflyConfig.getLocalMiddleware().getBinary() + " " + scriptName); | ||
} | ||
if (hoverflyConfig.getLogLevel().isPresent()) { | ||
commands.add("-log-level"); | ||
commands.add(hoverflyConfig.getLogLevel().get().name().toLowerCase()); | ||
} | ||
|
||
if (StringUtils.isNotBlank(hoverflyConfig.getUpstreamProxy())) { | ||
commands.add("-upstream-proxy"); | ||
commands.add(hoverflyConfig.getUpstreamProxy()); | ||
} | ||
if (hoverflyConfig.isMiddlewareEnabled()) { | ||
final String path = hoverflyConfig.getLocalMiddleware().getPath(); | ||
final String scriptName = path.contains(File.separator) ? path.substring(path.lastIndexOf(File.separator) + 1) : path; | ||
tempFileManager.copyClassPathResource(path, scriptName); | ||
commands.add("-middleware"); | ||
commands.add(hoverflyConfig.getLocalMiddleware().getBinary() + " " + scriptName); | ||
} | ||
|
||
try { | ||
startedProcess = new ProcessExecutor() | ||
.command(commands) | ||
.redirectOutput(hoverflyConfig.getHoverflyLogger().<OutputStream>map(LoggingOutputStream::new).orElse(System.out)) | ||
.directory(tempFileManager.getTempDirectory().toFile()) | ||
.start(); | ||
} catch (IOException e) { | ||
throw new IllegalStateException("Could not start Hoverfly process", e); | ||
if (StringUtils.isNotBlank(hoverflyConfig.getUpstreamProxy())) { | ||
commands.add("-upstream-proxy"); | ||
commands.add(hoverflyConfig.getUpstreamProxy()); | ||
} | ||
|
||
try { | ||
startedProcess = new ProcessExecutor() | ||
.command(commands) | ||
.redirectOutput(hoverflyConfig.getHoverflyLogger().<OutputStream>map(LoggingOutputStream::new).orElse(System.out)) | ||
.directory(tempFileManager.getTempDirectory().toFile()) | ||
.start(); | ||
} catch (IOException e) { | ||
throw new IllegalStateException("Could not start Hoverfly process", e); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -505,6 +515,16 @@ private void persistSimulation(Path path, Simulation simulation) throws IOExcept | |
JSON_PRETTY_PRINTER.writeValue(path.toFile(), simulation); | ||
} | ||
|
||
/** | ||
* Looks for an unused port on the current machine | ||
*/ | ||
private int findUnusedPort() { | ||
try (final ServerSocket serverSocket = new ServerSocket(0)) { | ||
return serverSocket.getLocalPort(); | ||
} catch (IOException e) { | ||
throw new RuntimeException("Cannot find available port", e); | ||
} | ||
} | ||
|
||
/** | ||
* Blocks until the Hoverfly process becomes healthy, otherwise time out | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should try not to remove public method hoverfly-java users depends on. For example, people may do the following to create a hoverfly client:
Why not create a new builder method to set config from a
HoverflyConfiguration
object, something like this: