Skip to content

Commit

Permalink
[#126] #IMPLEMENT 'assemblyName: DotNet.Testcontainers; function: Con…
Browse files Browse the repository at this point in the history
…tainerRegistry'

{Purge orphaned containers when the default application domain's parent process exits.}
  • Loading branch information
HofmeisterAn committed Oct 4, 2019
1 parent 3e21c90 commit e5fd0cb
Show file tree
Hide file tree
Showing 14 changed files with 101 additions and 27 deletions.
9 changes: 6 additions & 3 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ jobs:
# Run Windows build and tests.
- template: template/build.yml
parameters:
name: Windows
name: BuildAndTestOnWindows
displayName: Windows
vmImage: windows-2019
dotNetCoreVersion: $(dotNetCoreVersion)
cakeVersion: $(cakeVersion)
Expand All @@ -29,7 +30,8 @@ jobs:
# Run Linux build and tests.
- template: template/build.yml
parameters:
name: Linux
name: BuildAndTestOnLinux
displayName: Linux
vmImage: ubuntu-16.04
dotNetCoreVersion: $(dotNetCoreVersion)
cakeVersion: $(cakeVersion)
Expand All @@ -40,7 +42,8 @@ jobs:
- ${{ if in(variables['Build.SourceBranch'], 'refs/heads/master', 'refs/heads/develop') }}:
- template: template/publish.yml
parameters:
name: Linux
name: Release
displayName: Release
vmImage: ubuntu-16.04
dotNetCoreVersion: $(dotNetCoreVersion)
cakeVersion: $(cakeVersion)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace DotNet.Testcontainers.Tests.Unit.Linux
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using DotNet.Testcontainers.Core;
using DotNet.Testcontainers.Core.Archive;
using DotNet.Testcontainers.Core.Builder;
using ICSharpCode.SharpZipLib.Tar;
using Xunit;
Expand Down
36 changes: 36 additions & 0 deletions src/DotNet.Testcontainers/Clients/ContainerRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace DotNet.Testcontainers.Clients
{
using System.Collections.Generic;
using System.Linq;

internal static class ContainerRegistry
{
private static readonly object RegisteredContainersPadLock = new object();

private static readonly IDictionary<string, bool> RegisteredContainers = new Dictionary<string, bool>();

public static IEnumerable<string> GetRegisteredContainers()
{
lock (RegisteredContainersPadLock)
{
return RegisteredContainers.Where(registeredContainer => true.Equals(registeredContainer.Value)).Select(registeredContainer => registeredContainer.Key);
}
}

public static void Register(string id, bool cleanUp = false)
{
lock (RegisteredContainersPadLock)
{
RegisteredContainers.Add(id, cleanUp);
}
}

public static void Unregister(string id)
{
lock (RegisteredContainersPadLock)
{
RegisteredContainers.Remove(id);
}
}
}
}
4 changes: 2 additions & 2 deletions src/DotNet.Testcontainers/Clients/MetaDataClientContainers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ namespace DotNet.Testcontainers.Clients

internal sealed class MetaDataClientContainers : DockerMetaDataClient<ContainerListResponse>
{
private static readonly Lazy<MetaDataClientContainers> MetaDataClient = new Lazy<MetaDataClientContainers>(() => new MetaDataClientContainers());
private static readonly Lazy<MetaDataClientContainers> MetaDataClientLazy = new Lazy<MetaDataClientContainers>(() => new MetaDataClientContainers());

private MetaDataClientContainers()
{
}

internal static MetaDataClientContainers Instance { get; } = MetaDataClient.Value;
internal static MetaDataClientContainers Instance { get; } = MetaDataClientLazy.Value;

internal override async Task<IReadOnlyCollection<ContainerListResponse>> GetAllAsync()
{
Expand Down
4 changes: 2 additions & 2 deletions src/DotNet.Testcontainers/Clients/MetaDataClientImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ namespace DotNet.Testcontainers.Clients

internal sealed class MetaDataClientImages : DockerMetaDataClient<ImagesListResponse>
{
private static readonly Lazy<MetaDataClientImages> MetaDataClient = new Lazy<MetaDataClientImages>(() => new MetaDataClientImages());
private static readonly Lazy<MetaDataClientImages> MetaDataClientLazy = new Lazy<MetaDataClientImages>(() => new MetaDataClientImages());

private MetaDataClientImages()
{
}

internal static MetaDataClientImages Instance { get; } = MetaDataClient.Value;
internal static MetaDataClientImages Instance { get; } = MetaDataClientLazy.Value;

internal override async Task<IReadOnlyCollection<ImagesListResponse>> GetAllAsync()
{
Expand Down
4 changes: 2 additions & 2 deletions src/DotNet.Testcontainers/Clients/MetaDataClientSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ namespace DotNet.Testcontainers.Clients

internal sealed class MetaDataClientSystem : DockerApiClient
{
private static readonly Lazy<MetaDataClientSystem> MetaDataClient = new Lazy<MetaDataClientSystem>(() => new MetaDataClientSystem());
private static readonly Lazy<MetaDataClientSystem> MetaDataClientLazy = new Lazy<MetaDataClientSystem>(() => new MetaDataClientSystem());

private MetaDataClientSystem()
{
}

internal static MetaDataClientSystem Instance { get; } = MetaDataClient.Value;
internal static MetaDataClientSystem Instance { get; } = MetaDataClientLazy.Value;

internal bool IsWindowsEngineEnabled { get; } = Docker.System.GetSystemInfoAsync().GetAwaiter().GetResult().OperatingSystem.Contains("Windows");
}
Expand Down
34 changes: 29 additions & 5 deletions src/DotNet.Testcontainers/Clients/TestcontainersClient.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,49 @@
namespace DotNet.Testcontainers.Clients
{
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Core;
using DotNet.Testcontainers.Core.Archive;
using DotNet.Testcontainers.Core.Mapper;
using DotNet.Testcontainers.Core.Models;
using DotNet.Testcontainers.Core.Wait;
using DotNet.Testcontainers.Diagnostics;
using Microsoft.Extensions.Logging;

internal class TestcontainersClient : DockerApiClient, ITestcontainersClient
{
private static readonly Lazy<ITestcontainersClient> Testcontainers = new Lazy<ITestcontainersClient>(() => new TestcontainersClient());
private static readonly Lazy<ITestcontainersClient> TestcontainersClientLazy = new Lazy<ITestcontainersClient>(() => new TestcontainersClient());

private static readonly ILogger<TestcontainersClient> Log = TestcontainersHost.GetLogger<TestcontainersClient>();

private TestcontainersClient()
{
AppDomain.CurrentDomain.ProcessExit += (sender, args) => PurgeOrphanedContainers();
Console.CancelKeyPress += (sender, args) => PurgeOrphanedContainers();
}

internal static ITestcontainersClient Instance { get; } = Testcontainers.Value;
internal static ITestcontainersClient Instance { get; } = TestcontainersClientLazy.Value;

private static void PurgeOrphanedContainers()
{
var args = string.Join(" ", ContainerRegistry.GetRegisteredContainers());

if (string.IsNullOrEmpty(args))
{
return;
}

new Process { StartInfo = { FileName = "docker", Arguments = $"rm --force {args}" } }.Start();
}

public async Task StartAsync(string id, CancellationToken cancellationToken = default)
{
if (await MetaDataClientContainers.Instance.ExistsWithIdAsync(id))
{
await Docker.Containers.StartContainerAsync(id, new ContainerStartParameters { }, cancellationToken);
await Docker.Containers.StartContainerAsync(id, new ContainerStartParameters(), cancellationToken);
}
}

Expand All @@ -43,6 +61,8 @@ public async Task RemoveAsync(string id, CancellationToken cancellationToken = d
{
await Docker.Containers.RemoveContainerAsync(id, new ContainerRemoveParameters { Force = true }, cancellationToken);
}

ContainerRegistry.Unregister(id);
}

public async Task AttachAsync(string id, IOutputConsumer outputConsumer, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -155,7 +175,11 @@ public async Task<string> RunAsync(TestcontainersConfiguration config, Cancellat

await pullImageTask;

return (await Docker.Containers.CreateContainerAsync(createParameters, cancellationToken)).ID;
var id = (await Docker.Containers.CreateContainerAsync(createParameters, cancellationToken)).ID;

ContainerRegistry.Register(id, config.CleanUp);

return id;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace DotNet.Testcontainers.Core
namespace DotNet.Testcontainers.Core.Archive
{
using System;
using System.IO;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace DotNet.Testcontainers.Core
namespace DotNet.Testcontainers.Core.Archive
{
internal interface ITarArchive
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace DotNet.Testcontainers.Core.Containers

public class TestcontainersContainer : IDockerContainer
{
private const string ContainerIsNotRunning = "Testcontainer is not running.";

private bool disposed;

private string id;
Expand Down Expand Up @@ -49,7 +51,7 @@ public string Name
{
if (this.container == null)
{
throw new InvalidOperationException("Testcontainer is not running.");
throw new InvalidOperationException(ContainerIsNotRunning);
}

return this.container.Names.FirstOrDefault() ?? string.Empty;
Expand All @@ -62,7 +64,7 @@ public string IPAddress
{
if (this.container == null)
{
throw new InvalidOperationException("Testcontainer is not running.");
throw new InvalidOperationException(ContainerIsNotRunning);
}

var ipAddress = this.container.NetworkSettings.Networks.FirstOrDefault();
Expand All @@ -76,7 +78,7 @@ public string MacAddress
{
if (this.container == null)
{
throw new InvalidOperationException("Testcontainer is not running.");
throw new InvalidOperationException(ContainerIsNotRunning);
}

var macAddress = this.container.NetworkSettings.Networks.FirstOrDefault();
Expand All @@ -95,7 +97,7 @@ public int GetMappedPublicPort(string privatePort)
{
if (this.container == null)
{
throw new InvalidOperationException("Testcontainer is not running.");
throw new InvalidOperationException(ContainerIsNotRunning);
}

var mappedPort = this.container.Ports.FirstOrDefault(port => $"{port.PrivatePort}".Equals(privatePort));
Expand Down
4 changes: 2 additions & 2 deletions src/DotNet.Testcontainers/Diagnostics/DebugProgress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace DotNet.Testcontainers.Diagnostics

internal sealed class DebugProgress : IProgress<JSONMessage>
{
private static readonly Lazy<DebugProgress> Progress = new Lazy<DebugProgress>(() => new DebugProgress());
private static readonly Lazy<DebugProgress> DebugProgressLazy = new Lazy<DebugProgress>(() => new DebugProgress());

public static DebugProgress Instance => Progress.Value;
public static DebugProgress Instance => DebugProgressLazy.Value;

public void Report(JSONMessage value)
{
Expand Down
6 changes: 6 additions & 0 deletions src/DotNet.Testcontainers/TestcontainersHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace DotNet.Testcontainers
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using ILogger = Microsoft.Extensions.Logging.ILogger;

internal static class TestcontainersHost
{
Expand All @@ -25,6 +26,11 @@ static TestcontainersHost()
.Build();
}

internal static ILogger GetLogger(string categoryName)
{
return host.Services.GetRequiredService<ILoggerFactory>().CreateLogger(categoryName);
}

internal static ILogger<T> GetLogger<T>()
{
return host.Services.GetRequiredService<ILogger<T>>();
Expand Down
2 changes: 1 addition & 1 deletion template/build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
jobs:
- job: ${{ parameters.name }}
displayName: CI build for ${{ parameters.name }}
displayName: CI build for ${{ parameters.displayName }}

pool:
vmImage: ${{ parameters.vmImage }}
Expand Down
9 changes: 6 additions & 3 deletions template/publish.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
jobs:
- job: ${{ parameters.name }}
displayName: CI build for ${{ parameters.name }}
displayName: CI build for ${{ parameters.displayName }}

pool:
vmImage: ${{ parameters.vmImage }}

dependsOn:
- Windows
- Linux
- BuildAndTestOnWindows
- BuildAndTestOnLinux

steps:
- task: UseDotNet@2
displayName: Use .NET Core SDK ${{ parameters.dotNetCoreVersion }}
inputs:
version: ${{ parameters.dotNetCoreVersion }}

- powershell: dotnet tool install --tool-path ./tools --version ${{ parameters.cakeVersion }} Cake.Tool
displayName: Setup prerequisites

- powershell: ./tools/dotnet-cake --target=Sonar
displayName: Sonar
env:
Expand Down

0 comments on commit e5fd0cb

Please sign in to comment.