Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3b44061
adding support for a service container docker logs
AvaStancu Aug 30, 2022
2e8d8a7
Adding Unit test to ContainerOperationProvider
JoannaaKL Aug 31, 2022
160d07e
Adding another test to ContainerOperationProvider
JoannaaKL Aug 31, 2022
ae7bb31
placed the docker logs output in dedicated ##group section
AvaStancu Sep 6, 2022
892a90c
Removed the exception thrown if the service container was not healthy
JoannaaKL Sep 6, 2022
d533271
Removed duplicated logging to the executionContext
JoannaaKL Sep 6, 2022
29e26c0
Updated the container logs sub-section message
AvaStancu Sep 6, 2022
2f9ced9
Print service containers only if they were healthy
AvaStancu Sep 6, 2022
94e504c
Removed recently added method to inspect docker logs
AvaStancu Sep 6, 2022
cfcf083
Added execution context error
AvaStancu Sep 6, 2022
2d4dc37
Removing the section 'Waiting for all services to be ready'
JoannaaKL Sep 7, 2022
ad8f17e
Update src/Runner.Worker/Container/DockerCommandManager.cs
JoannaaKL Sep 7, 2022
354c8bc
Update src/Test/L0/TestHostContext.cs
JoannaaKL Sep 7, 2022
f0b315c
Change the logic for printing Service Containers logs
JoannaaKL Sep 8, 2022
2dc8f25
Removed unused import
JoannaaKL Sep 8, 2022
dfcbe9b
Added back section group.
JoannaaKL Sep 8, 2022
7a992f8
Moved service containers error logs to separate group sections
AvaStancu Sep 9, 2022
76e4f51
Removed the test testing the old logic flow.
JoannaaKL Sep 9, 2022
912d7d6
Remove unnecessary 'IsAnyUnhealthy' flag
JoannaaKL Sep 13, 2022
f5461b7
Remove printHello() function
JoannaaKL Sep 13, 2022
3c88e14
Add newline to TestHostContext
JoannaaKL Sep 13, 2022
47ead99
Remove unnecessary field 'UnhealthyContainers'
JoannaaKL Sep 13, 2022
b8aafc4
Rename boolean flag indicating service container failure
JoannaaKL Sep 13, 2022
826cec2
Refactor healthcheck logic to separate method to enable unit testing.
JoannaaKL Sep 14, 2022
e03ad87
Remove the default value for bool variable
JoannaaKL Sep 14, 2022
5b26a1e
Update src/Runner.Worker/ContainerOperationProvider.cs
JoannaaKL Sep 15, 2022
a9fa7f8
Update src/Runner.Worker/ContainerOperationProvider.cs
JoannaaKL Sep 15, 2022
b15525a
Rename Healthcheck back to ContainerHealthcheck
JoannaaKL Sep 15, 2022
72fe579
Make test sequential
JoannaaKL Sep 15, 2022
7ba0916
Unextract the container error logs method
JoannaaKL Sep 15, 2022
805d6fd
remove test asserting thrown exception
JoannaaKL Sep 15, 2022
3b30e5c
Add configure await
JoannaaKL Sep 15, 2022
d3f463b
Update src/Test/L0/Worker/ContainerOperationProviderL0.cs
JoannaaKL Sep 16, 2022
75c40e3
Update src/Test/L0/Worker/ContainerOperationProviderL0.cs
JoannaaKL Sep 16, 2022
b1dd797
Update src/Test/L0/Worker/ContainerOperationProviderL0.cs
JoannaaKL Sep 16, 2022
82b81f2
Update src/Test/L0/Worker/ContainerOperationProviderL0.cs
JoannaaKL Sep 16, 2022
c5685b7
Update src/Test/L0/Worker/ContainerOperationProviderL0.cs
JoannaaKL Sep 16, 2022
1778f8f
Add back test asserting exception
JoannaaKL Sep 16, 2022
fec24e8
Check service exit code if there is no healtcheck configured
JoannaaKL Sep 16, 2022
f39a18d
Remove unnecessary healthcheck for healthy service container
JoannaaKL Sep 27, 2022
0d782f4
Revert "Check service exit code if there is no healtcheck configured"
JoannaaKL Oct 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Runner.Worker/Container/ContainerInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ public ContainerInfo(IHostContext hostContext, Pipelines.JobContainer container,
public bool IsJobContainer { get; set; }
public bool IsAlpine { get; set; }

public bool FailedInitialization { get; set; }

public IDictionary<string, string> ContainerEnvironmentVariables
{
get
Expand Down
59 changes: 40 additions & 19 deletions src/Runner.Worker/ContainerOperationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,41 @@ public async Task StartContainersAsync(IExecutionContext executionContext, objec
await StartContainerAsync(executionContext, container);
}

await RunContainersHealthcheck(executionContext, containers);
}

public async Task RunContainersHealthcheck(IExecutionContext executionContext, List<ContainerInfo> containers)
{
executionContext.Output("##[group]Waiting for all services to be ready");

var unhealthyContainers = new List<ContainerInfo>();
foreach (var container in containers.Where(c => !c.IsJobContainer))
{
await ContainerHealthcheck(executionContext, container);
var healthcheck = await ContainerHealthcheck(executionContext, container);

if (!string.Equals(healthcheck, "healthy", StringComparison.OrdinalIgnoreCase))
{
unhealthyContainers.Add(container);
}
else
{
executionContext.Output($"{container.ContainerNetworkAlias} service is healthy.");
}
}
executionContext.Output("##[endgroup]");

if (unhealthyContainers.Count > 0)
{
foreach (var container in unhealthyContainers)
{
executionContext.Output($"##[group]Service container {container.ContainerNetworkAlias} failed.");
await _dockerManager.DockerLogs(context: executionContext, containerId: container.ContainerId);
executionContext.Error($"Failed to initialize container {container.ContainerImage}");
container.FailedInitialization = true;
executionContext.Output("##[endgroup]");
}
throw new InvalidOperationException("One or more containers failed to start.");
}
}

public async Task StopContainersAsync(IExecutionContext executionContext, object data)
Expand Down Expand Up @@ -299,16 +328,15 @@ private async Task StopContainerAsync(IExecutionContext executionContext, Contai

if (!string.IsNullOrEmpty(container.ContainerId))
{
if (!container.IsJobContainer)
if (!container.IsJobContainer && !container.FailedInitialization)
{
// Print logs for service container jobs (not the "action" job itself b/c that's already logged).
executionContext.Output($"Print service container logs: {container.ContainerDisplayName}");
executionContext.Output($"Print service container logs: {container.ContainerDisplayName}");

int logsExitCode = await _dockerManager.DockerLogs(executionContext, container.ContainerId);
if (logsExitCode != 0)
{
executionContext.Warning($"Docker logs fail with exit code {logsExitCode}");
}
int logsExitCode = await _dockerManager.DockerLogs(executionContext, container.ContainerId);
if (logsExitCode != 0)
{
executionContext.Warning($"Docker logs fail with exit code {logsExitCode}");
}
}

executionContext.Output($"Stop and remove container: {container.ContainerDisplayName}");
Expand Down Expand Up @@ -395,14 +423,14 @@ private async Task RemoveContainerNetworkAsync(IExecutionContext executionContex
}
}

private async Task ContainerHealthcheck(IExecutionContext executionContext, ContainerInfo container)
private async Task<string> ContainerHealthcheck(IExecutionContext executionContext, ContainerInfo container)
{
string healthCheck = "--format=\"{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}\"";
string serviceHealth = (await _dockerManager.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
if (string.IsNullOrEmpty(serviceHealth))
{
// Container has no HEALTHCHECK
return;
return String.Empty;
}
var retryCount = 0;
while (string.Equals(serviceHealth, "starting", StringComparison.OrdinalIgnoreCase))
Expand All @@ -413,14 +441,7 @@ private async Task ContainerHealthcheck(IExecutionContext executionContext, Cont
serviceHealth = (await _dockerManager.DockerInspect(context: executionContext, dockerObject: container.ContainerId, options: healthCheck)).FirstOrDefault();
retryCount++;
}
if (string.Equals(serviceHealth, "healthy", StringComparison.OrdinalIgnoreCase))
{
executionContext.Output($"{container.ContainerNetworkAlias} service is healthy.");
}
else
{
throw new InvalidOperationException($"Failed to initialize, {container.ContainerNetworkAlias} service is {serviceHealth}.");
}
return serviceHealth;
}

private async Task<string> ContainerRegistryLogin(IExecutionContext executionContext, ContainerInfo container)
Expand Down
108 changes: 108 additions & 0 deletions src/Test/L0/Worker/ContainerOperationProviderL0.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Container;
using Xunit;
using Moq;
using GitHub.Runner.Worker.Container.ContainerHooks;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using GitHub.DistributedTask.WebApi;
using System;

namespace GitHub.Runner.Common.Tests.Worker
{

public sealed class ContainerOperationProviderL0
{

private TestHostContext _hc;
private Mock<IExecutionContext> _ec;
private Mock<IDockerCommandManager> _dockerManager;
private Mock<IContainerHookManager> _containerHookManager;
private ContainerOperationProvider containerOperationProvider;
private Mock<IJobServerQueue> serverQueue;
private Mock<IPagingLogger> pagingLogger;
private List<string> healthyDockerStatus = new List<string> { "healthy" };
private List<string> unhealthyDockerStatus = new List<string> { "unhealthy" };
private List<string> dockerLogs = new List<string> { "log1", "log2", "log3" };

List<ContainerInfo> containers = new List<ContainerInfo>();

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async void RunServiceContainersHealthcheck_UnhealthyServiceContainer_AssertFailedTask()
{
//Arrange
Setup();
_dockerManager.Setup(x => x.DockerInspect(_ec.Object, It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(unhealthyDockerStatus));

//Act
try
{
await containerOperationProvider.RunContainersHealthcheck(_ec.Object, containers);
}
catch (InvalidOperationException)
{

//Assert
Assert.Equal(TaskResult.Failed, _ec.Object.Result ?? TaskResult.Failed);
}
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async void RunServiceContainersHealthcheck_UnhealthyServiceContainer_AssertExceptionThrown()
{
//Arrange
Setup();
_dockerManager.Setup(x => x.DockerInspect(_ec.Object, It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(unhealthyDockerStatus));

//Act and Assert
await Assert.ThrowsAsync<InvalidOperationException>(() => containerOperationProvider.RunContainersHealthcheck(_ec.Object, containers));

}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async void RunServiceContainersHealthcheck_healthyServiceContainer_AssertSucceededTask()
{
//Arrange
Setup();
_dockerManager.Setup(x => x.DockerInspect(_ec.Object, It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(healthyDockerStatus));

//Act
await containerOperationProvider.RunContainersHealthcheck(_ec.Object, containers);

//Assert
Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded);

}

private void Setup([CallerMemberName] string testName = "")
{
containers.Add(new ContainerInfo() { ContainerImage = "ubuntu:16.04" });
_hc = new TestHostContext(this, testName);
_ec = new Mock<IExecutionContext>();
serverQueue = new Mock<IJobServerQueue>();
pagingLogger = new Mock<IPagingLogger>();

_dockerManager = new Mock<IDockerCommandManager>();
_containerHookManager = new Mock<IContainerHookManager>();
containerOperationProvider = new ContainerOperationProvider();

_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
_hc.SetSingleton<IJobServerQueue>(serverQueue.Object);
_hc.SetSingleton<IPagingLogger>(pagingLogger.Object);

_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
_hc.SetSingleton<IContainerHookManager>(_containerHookManager.Object);

_ec.Setup(x => x.Global).Returns(new GlobalContext());

containerOperationProvider.Initialize(_hc);
}
}
}