From 8001a2c26447eaaac8508364e4fd77acd928255d Mon Sep 17 00:00:00 2001 From: Valery Yatsynovich Date: Tue, 4 Jun 2024 17:01:34 +0300 Subject: [PATCH] Add tunnel readiness check after SauceConnect start (#258) --- .../AbstractSauceTunnelManager.java | 38 +++++++++++++++++-- .../SauceConnectFourManagerTest.java | 20 ++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/saucelabs/ci/sauceconnect/AbstractSauceTunnelManager.java b/src/main/java/com/saucelabs/ci/sauceconnect/AbstractSauceTunnelManager.java index 55e1a42..6e0899c 100644 --- a/src/main/java/com/saucelabs/ci/sauceconnect/AbstractSauceTunnelManager.java +++ b/src/main/java/com/saucelabs/ci/sauceconnect/AbstractSauceTunnelManager.java @@ -10,6 +10,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -34,6 +35,8 @@ public abstract class AbstractSauceTunnelManager implements SauceTunnelManager { protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractSauceTunnelManager.class); + private static final Duration READINESS_CHECK_TIMEOUT = Duration.ofSeconds(15); + private static final Duration READINESS_CHECK_POLLING_INTERVAL = Duration.ofSeconds(3); /** Should Sauce Connect output be suppressed? */ protected boolean quietMode; @@ -498,8 +501,10 @@ public Process openConnection( } else { // everything okay, continue the build - if (outputGobbler.getTunnelId() != null) { - tunnelInformation.setTunnelId(outputGobbler.getTunnelId()); + String provisionedTunnelId = outputGobbler.getTunnelId(); + if (provisionedTunnelId != null) { + tunnelInformation.setTunnelId(provisionedTunnelId); + waitForReadiness(provisionedTunnelId); } logMessage( printStream, "Sauce Connect " + getCurrentVersion() + " now launched for: " + name); @@ -537,7 +542,34 @@ public Process openConnection( } } - public SystemErrorGobbler makeErrorGobbler(PrintStream printStream, InputStream errorStream) { + private void waitForReadiness(String tunnelId) { + long pollingIntervalMillis = READINESS_CHECK_POLLING_INTERVAL.toMillis(); + long endTime = System.currentTimeMillis() + READINESS_CHECK_TIMEOUT.toMillis(); + try { + do { + long iterationStartTime = System.currentTimeMillis(); + Boolean isReady = scEndpoint.getTunnelInformation(tunnelId).isReady; + if (Boolean.TRUE.equals(isReady)) { + LOGGER.info("Tunnel with ID {} is ready for use", tunnelId); + return; + } + LOGGER.info("Waiting for readiness of tunnel with ID {}", tunnelId); + long iterationEndTime = System.currentTimeMillis(); + + long iterationPollingTimeout = pollingIntervalMillis - (iterationEndTime - iterationStartTime); + if (iterationPollingTimeout > 0) { + TimeUnit.MILLISECONDS.sleep(iterationPollingTimeout); + } + } + while (System.currentTimeMillis() <= endTime); + LOGGER.warn("Wait for readiness of tunnel with ID {} is timed out", tunnelId); + } + catch (IOException | InterruptedException e) { + LOGGER.warn("Unable to check readiness of tunnel with ID {}", tunnelId, e); + } + } + + public SystemErrorGobbler makeErrorGobbler(PrintStream printStream, InputStream errorStream) { return new SystemErrorGobbler("ErrorGobbler", errorStream, printStream); } diff --git a/src/test/java/com/saucelabs/ci/sauceconnect/SauceConnectFourManagerTest.java b/src/test/java/com/saucelabs/ci/sauceconnect/SauceConnectFourManagerTest.java index c0a45bb..34a615c 100755 --- a/src/test/java/com/saucelabs/ci/sauceconnect/SauceConnectFourManagerTest.java +++ b/src/test/java/com/saucelabs/ci/sauceconnect/SauceConnectFourManagerTest.java @@ -37,6 +37,9 @@ @ExtendWith(MockitoExtension.class) class SauceConnectFourManagerTest { + private static final String STARTED_SC_LOG = "/started_sc.log"; + private static final String STARTED_TUNNEL_ID = "a3ccd3985ed04e7ba0fefc7fa401e9c8"; + @Mock private Process mockProcess; @Mock private SauceREST mockSauceRest; @Mock private SauceConnectEndpoint mockSCEndpoint; @@ -58,8 +61,11 @@ private InputStream getResourceAsStream(String resourceName) { @ValueSource(booleans = {true, false}) void testOpenConnectionSuccessfully(boolean cleanUpOnExit) throws IOException { when(mockSCEndpoint.getTunnelsInformationForAUser()).thenReturn(List.of()); + TunnelInformation readyTunnel = new TunnelInformation(); + readyTunnel.isReady = true; + when(mockSCEndpoint.getTunnelInformation(STARTED_TUNNEL_ID)).thenReturn(readyTunnel); tunnelManager.setCleanUpOnExit(cleanUpOnExit); - Process process = testOpenConnection("/started_sc.log"); + Process process = testOpenConnection(STARTED_SC_LOG); assertEquals(mockProcess, process); } @@ -75,7 +81,13 @@ void openConnectionTest_closes() throws IOException, InterruptedException { @Test void testOpenConnectionWithExtraSpacesInArgs() throws IOException { when(mockSCEndpoint.getTunnelsInformationForAUser()).thenReturn(List.of()); - testOpenConnection("/started_sc.log", " username-with-spaces-around "); + TunnelInformation notReadyTunnel = new TunnelInformation(); + notReadyTunnel.isReady = false; + TunnelInformation readyTunnel = new TunnelInformation(); + readyTunnel.isReady = true; + when(mockSCEndpoint.getTunnelInformation(STARTED_TUNNEL_ID)).thenReturn(notReadyTunnel, + readyTunnel); + testOpenConnection(STARTED_SC_LOG, " username-with-spaces-around "); } private Process testOpenConnection(String logFile) throws IOException { @@ -121,10 +133,12 @@ void openConnectionTest_existing_tunnel() throws IOException { TunnelInformation started = new TunnelInformation(); started.tunnelIdentifier = "8949e55fb5e14fd6bf6230b7a609b494"; started.status = "running"; + started.isReady = true; when(mockSCEndpoint.getTunnelsInformationForAUser()).thenReturn(List.of(started)); + when(mockSCEndpoint.getTunnelInformation(STARTED_TUNNEL_ID)).thenReturn(started); - Process process = testOpenConnection("/started_sc.log"); + Process process = testOpenConnection(STARTED_SC_LOG); assertEquals(mockProcess, process); verify(mockSCEndpoint).getTunnelsInformationForAUser();