private static async Task <int> GetHostPortAsync(DockerCli dockerCli, string containerName, int portInContainer) { // We are depending on Docker to open ports in the host dynamically in order for our tests to be able to // run in parallel without worrying about port collision and flaky tests. However it appears that Docker // opens up a port in the host only after an attempt was made to open up a port in the container. Since a // port in the container could open up late because of the way a web application works (like it might be // initializing something which could take time), we are introducing delay for the application to be up // before trying to invoke the 'docker port' command. for (var i = 0; i < MaxRetryCount; i++) { await Task.Delay(DelayBetweenRetries); // Example output from `docker port <container-name> <container-port>`: // "0.0.0.0:32774" var getPortMappingResult = dockerCli.GetPortMapping(containerName, portInContainer); if (getPortMappingResult.ExitCode == 0) { var stdOut = getPortMappingResult.StdOut?.Trim().ReplaceNewLine(); var portMapping = stdOut?.Split(":"); Assert.NotNull(portMapping); Assert.True( (portMapping.Length == 2), "Did not get the port mapping in expected format. StdOut: " + getPortMappingResult.StdOut); var hostPort = Convert.ToInt32(portMapping[1]); return(hostPort); } else if (getPortMappingResult.StdErr.Contains("No such container")) { break; } } throw new InvalidOperationException($"Could not retreive the host port of the container {containerName}:{portInContainer}"); }
public static DockerRunCommandResult Run( this DockerCli dockerCli, string imageId, EnvironmentVariable environmentVariable, DockerVolume volume, string portMapping, string link, bool runContainerInBackground, string commandToExecuteOnRun, string[] commandArguments) { var environmentVariables = new List <EnvironmentVariable>(); if (environmentVariable != null) { environmentVariables.Add(environmentVariable); } var volumes = new List <DockerVolume>(); if (volume != null) { volumes.Add(volume); } return(dockerCli.Run( imageId, environmentVariables, volumes, portMapping, link, runContainerInBackground, commandToExecuteOnRun, commandArguments)); }
// The sequence of steps are: // 1. Copies the sample app from host machine's git repo to a different folder (under 'tmp/<reserved-name>' folder // in CI machine and in the 'bin\debug\' folder in non-CI machine). The copying is necessary to not muck // with git tracked samples folder and also a single sample could be used for verification in multiple tests. // 2. Volume mounts the directory to the build image and build it. // 3. Volume mounts the same directory to runtime image and runs the application. // 4. A func supplied by the user is retried to the max of 10 retries between a delay of 1 second. public static async Task BuildRunAndAssertAppAsync( ITestOutputHelper output, IEnumerable <DockerVolume> volumes, string buildImage, string buildCmd, string[] buildArgs, string runtimeImageName, List <EnvironmentVariable> environmentVariables, int port, string link, string runCmd, string[] runArgs, Func <int, Task> assertAction) { var dockerCli = new DockerCli(); // Build var buildAppResult = dockerCli.Run(new DockerRunArguments { ImageId = buildImage, EnvironmentVariables = environmentVariables, Volumes = volumes, RunContainerInBackground = false, CommandToExecuteOnRun = buildCmd, CommandArguments = buildArgs, }); await RunAssertsAsync( () => { Assert.True(buildAppResult.IsSuccess); return(Task.CompletedTask); }, buildAppResult, output); // Run await RunAndAssertAppAsync( runtimeImageName, output, volumes, environmentVariables, port, link, runCmd, runArgs, assertAction, dockerCli); }
// The sequence of steps are: // 1. Copies the sample app from host machine's git repo to a different folder (under 'tmp/<reserved-name>' folder // in CI machine and in the 'bin\debug\' folder in non-CI machine). The copying is necessary to not muck // with git tracked samples folder and also a single sample could be used for verification in multiple tests. // 2. Volume mounts the directory to the build image and build it. // 3. Volume mounts the same directory to runtime image and runs the application. // 4. A func supplied by the user is retried to the max of 10 retries between a delay of 1 second. public static async Task BuildRunAndAssertAppAsync( ITestOutputHelper output, List <DockerVolume> volumes, string buildCmd, string[] buildArgs, string runtimeImageName, List <EnvironmentVariable> environmentVariables, string portMapping, string link, string runCmd, string[] runArgs, Func <Task> assertAction) { var dockerCli = new DockerCli(); // Build var buildAppResult = dockerCli.Run( Settings.BuildImageName, environmentVariables: environmentVariables, volumes: volumes, portMapping: null, link: null, runContainerInBackground: false, commandToExecuteOnRun: buildCmd, commandArguments: buildArgs); await RunAssertsAsync( () => { Assert.True(buildAppResult.IsSuccess); return(Task.CompletedTask); }, buildAppResult, output); // Run await RunAndAssertAppAsync( runtimeImageName, output, volumes, environmentVariables, portMapping, link, runCmd, runArgs, assertAction, dockerCli); }
public static DockerRunCommandResult Run( this DockerCli dockerCli, string imageId, string commandToExecuteOnRun, string[] commandArguments) { return(Run( dockerCli, imageId, environmentVariable: null, volume: null, portMapping: null, link: null, runContainerInBackground: false, commandToExecuteOnRun, commandArguments)); }
public static DockerRunCommandResult Run( this DockerCli dockerCli, string imageId, List <EnvironmentVariable> environmentVariables, List <DockerVolume> volumes, string portMapping, string link, bool runContainerInBackground, string commandToExecuteOnRun, string[] commandArguments) { return(dockerCli.Run( imageId, environmentVariables, volumes, portMapping, link, runContainerInBackground, commandToExecuteOnRun, commandArguments)); }
public static async Task RunAndAssertAppAsync( string imageName, ITestOutputHelper output, IEnumerable <DockerVolume> volumes, List <EnvironmentVariable> environmentVariables, int port, string link, string runCmd, string[] runArgs, Func <int, Task> assertAction, DockerCli dockerCli) { DockerRunCommandProcessResult runResult = null; var showDebugInfo = true; try { // Docker run the runtime container as a foreground process. This way we can catch any errors // that might occur when the application is being started. runResult = dockerCli.RunAndDoNotWaitForProcessExit(new DockerRunArguments { ImageId = imageName, EnvironmentVariables = environmentVariables, Volumes = volumes, PortInContainer = port, Link = link, CommandToExecuteOnRun = runCmd, CommandArguments = runArgs, }); // An exception could have occurred when a Docker process failed to start. Assert.Null(runResult.Exception); Assert.False(runResult.Process.HasExited); var hostPort = await GetHostPortAsync(dockerCli, runResult.ContainerName, portInContainer : port); for (var i = 0; i < MaxRetryCount; i++) { try { // Make sure the process is still alive and fail fast if not. Assert.False(runResult.Process.HasExited); await assertAction(hostPort); showDebugInfo = false; break; } catch (Exception ex) when(ex.InnerException is IOException || ex.InnerException is SocketException) { if (i == MaxRetryCount - 1) { throw; } await Task.Delay(DelayBetweenRetries); } } } finally { if (runResult != null) { // Stop the container so that shared resources (like ports) are disposed. dockerCli.StopContainer(runResult.ContainerName); // Access the output and error streams after the process has exited if (showDebugInfo) { output.WriteLine(runResult.GetDebugInfo()); } } } }
// The sequence of steps are: // 1. Copies the sample app from host machine's git repo to a different folder (under 'tmp/<reserved-name>' folder // in CI machine and in the 'bin\debug\' folder in non-CI machine). The copying is necessary to not muck // with git tracked samples folder and also a single sample could be used for verification in multiple tests. // 2. Volume mounts the directory to the build image and build it. // 3. Volume mounts the same directory to runtime image and runs the application. // 4. A func supplied by the user is retried to the max of 10 retries between a delay of 1 second. public static async Task BuildRunAndAssertAppAsync( ITestOutputHelper output, DockerVolume volume, string buildCmd, string[] buildArgs, string runtimeImageName, List <EnvironmentVariable> environmentVariables, string portMapping, string link, string runCmd, string[] runArgs, Func <Task> assertAction) { var dockerCli = new DockerCli(); // Build var buildAppResult = dockerCli.Run( Settings.BuildImageName, volume, commandToExecuteOnRun: buildCmd, commandArguments: buildArgs); await RunAssertsAsync( () => { Assert.True(buildAppResult.IsSuccess); return(Task.CompletedTask); }, buildAppResult.GetDebugInfo()); // Run DockerRunCommandProcessResult runResult = null; try { // Docker run the runtime container as a foreground process. This way we can catch any errors // that might occur when the application is being started. runResult = dockerCli.RunAndDoNotWaitForProcessExit( runtimeImageName, environmentVariables, volumes: new List <DockerVolume> { volume }, portMapping, link, runCmd, runArgs); await RunAssertsAsync( () => { // An exception could have occurred when a docker process failed to start. Assert.Null(runResult.Exception); Assert.False(runResult.Process.HasExited); return(Task.CompletedTask); }, runResult.GetDebugInfo()); for (var i = 0; i < MaxRetryCount; i++) { await Task.Delay(TimeSpan.FromSeconds(DelayBetweenRetriesInSeconds)); try { // Make sure the process is still alive and fail fast if not alive. await RunAssertsAsync( async() => { Assert.False(runResult.Process.HasExited); await assertAction(); }, runResult.GetDebugInfo()); break; } catch (Exception ex) when(ex.InnerException is IOException || ex.InnerException is SocketException) { if (i == MaxRetryCount - 1) { output.WriteLine(runResult.GetDebugInfo()); throw; } } } } finally { if (runResult != null && runResult.Exception == null) { // Stop the container so that shared resources (like ports) are disposed. dockerCli.StopContainer(runResult.ContainerName); } } async Task RunAssertsAsync(Func <Task> action, string message) { try { await action(); } catch (Exception) { output.WriteLine(message); throw; } } }