Skip to content
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

ListContainersAsync response contains inconsistent public host ports #565

Open
HofmeisterAn opened this issue Jun 23, 2022 · 1 comment
Open

Comments

@HofmeisterAn
Copy link
Contributor

Output of dotnet --info:

.NET SDK (reflecting any global.json):
 Version:   6.0.300
 Commit:    8473146e7d

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19044
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\6.0.300\

Host (useful for support):
  Version: 6.0.5
  Commit:  70ae3df4a6

.NET SDKs installed:
  6.0.300 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download

What version of Docker.DotNet?:

3.125.5

Steps to reproduce the issue:

public sealed class ExampleContainer
{
  private static readonly IDockerClient DockerClient = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")).CreateClient();

  private static readonly CreateContainerParameters CreateContainerParameters = new CreateContainerParameters();

  private readonly ISet<string> hostPorts = new HashSet<string>();

  private int tryCount;

  private string ID;

  static ExampleContainer()
  {
    var hostConfig = new HostConfig();
    hostConfig.PortBindings = new Dictionary<string, IList<PortBinding>> { { "80/tcp", GetFailureConfig() } };
    CreateContainerParameters.Image = "nginx";
    CreateContainerParameters.HostConfig = hostConfig;
  }

  [Fact]
  public Task ShouldReturnOnePublicHostPort()
  {
    return Task.WhenAll(Enumerable.Range(0, 25)
      .Select(_ => new ExampleContainer())
      .Select(example => Task.Run(async () =>
      {
        try
        {
          await example.InitializeAsync()
            .ConfigureAwait(false);
          await example.GetPublicHostPort()
            .ConfigureAwait(false);
        }
        finally
        {
          await example.DisposeAsync()
            .ConfigureAwait(false);
        }
      })));
  }

  private static IList<PortBinding> GetSuccessConfig()
  {
    return new[] { new PortBinding { HostPort = "0" } };
  }

  private static IList<PortBinding> GetFailureConfig()
  {
    return new[] { new PortBinding { HostPort = null } };
  }

  private async Task InitializeAsync()
  {
    var createContainerResponse = await DockerClient.Containers.CreateContainerAsync(CreateContainerParameters)
      .ConfigureAwait(false);
    this.ID = createContainerResponse.ID;

    _ = await DockerClient.Containers.StartContainerAsync(this.ID, new ContainerStartParameters())
      .ConfigureAwait(false);
  }

  private Task DisposeAsync()
  {
    var containerRemoveParameters = new ContainerRemoveParameters();
    containerRemoveParameters.Force = true;
    return DockerClient.Containers.RemoveContainerAsync(this.ID, containerRemoveParameters);
  }

  private async Task GetPublicHostPort()
  {
    do
    {
      var containersListParameters = new ContainersListParameters();
      containersListParameters.All = true;
      containersListParameters.Filters = new Dictionary<string, IDictionary<string, bool>>();
      containersListParameters.Filters.Add("id", new Dictionary<string, bool> { { this.ID, true } });

      var listContainersResponse = await DockerClient.Containers.ListContainersAsync(containersListParameters)
        .ConfigureAwait(false);

      foreach (var publicHostPort in listContainersResponse.Single().Ports.Select(portBindings => portBindings.PublicPort))
      {
        this.hostPorts.Add(publicHostPort.ToString(CultureInfo.InvariantCulture));
      }

      if (this.hostPorts.Count > 1)
      {
        throw new InvalidOperationException(string.Join(',', this.hostPorts));
      }
    }
    while (++this.tryCount < 10);
  }
}

What actually happened?:

The response of ListContainersAsync sometimes contains for a single port binding multiple public host ports. In this case, a service behind the mapped port is not available. It looks like setting the port binding host port to null (assign random port) breaks it. I attached a reproducible example above. E.g. L:89 throws: System.InvalidOperationException: 49196,52481.

  • ... new[] { new PortBinding { HostPort = null } }: fails
  • ... new[] { new PortBinding { HostPort = "0" } }: successful

This also breaks InspectContainerAsync. In this case the response (list of port bindings) is empty for at least the first call.

What did you expect to happen?:

A consistent response.

Additional information:

-

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant