mirror of https://github.com/dotnet/tye.git
Browse Source
* Purge hanging replicas * Use dictionary for event serialization * Subscribing to replica events instead of waiting for arbitrary time in tests * Only return list of running containers from DockerAssert * use IDictionary instead of deserializing to ReplicaStatus when removing stale replicas * Exit logs loop in DockerRunner if container is killedpull/242/head
committed by
GitHub
8 changed files with 486 additions and 12 deletions
@ -0,0 +1,104 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Text.Json; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Tye.Hosting.Model; |
|||
using Microsoft.Extensions.Logging; |
|||
using System.Threading; |
|||
using System.Collections.Concurrent; |
|||
|
|||
namespace Microsoft.Tye.Hosting |
|||
{ |
|||
public class ReplicaRegistry : IDisposable |
|||
{ |
|||
private readonly ILogger _logger; |
|||
private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileWriteSemaphores; |
|||
private readonly string _tyeFolderPath; |
|||
|
|||
public ReplicaRegistry(Model.Application application, ILogger logger) |
|||
{ |
|||
_logger = logger; |
|||
_fileWriteSemaphores = new ConcurrentDictionary<string, SemaphoreSlim>(); |
|||
_tyeFolderPath = Path.Join(Path.GetDirectoryName(application.Source), ".tye"); |
|||
|
|||
if (!Directory.Exists(_tyeFolderPath)) |
|||
{ |
|||
Directory.CreateDirectory(_tyeFolderPath); |
|||
} |
|||
} |
|||
|
|||
public bool WriteReplicaEvent(string storeName, IDictionary<string, string> replicaRecord) |
|||
{ |
|||
var filePath = Path.Join(_tyeFolderPath, GetStoreFile(storeName)); |
|||
var contents = JsonSerializer.Serialize(replicaRecord, new JsonSerializerOptions { WriteIndented = false }); |
|||
var semaphore = GetSempahoreForStore(storeName); |
|||
|
|||
semaphore.Wait(); |
|||
try |
|||
{ |
|||
File.AppendAllText(filePath, contents + Environment.NewLine); |
|||
return true; |
|||
} |
|||
catch (DirectoryNotFoundException ex) |
|||
{ |
|||
_logger.LogWarning(ex, "tye folder is not found. file: {file}", filePath); |
|||
return false; |
|||
} |
|||
finally |
|||
{ |
|||
semaphore.Release(); |
|||
} |
|||
} |
|||
|
|||
public bool DeleteStore(string storeName) |
|||
{ |
|||
var filePath = Path.Join(_tyeFolderPath, GetStoreFile(storeName)); |
|||
|
|||
try |
|||
{ |
|||
File.Delete(storeName); |
|||
return true; |
|||
} |
|||
catch (DirectoryNotFoundException ex) |
|||
{ |
|||
_logger.LogWarning(ex, "tye folder is not found. file: {file}", filePath); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public async ValueTask<IList<IDictionary<string, string>>> GetEvents(string storeName) |
|||
{ |
|||
var filePath = Path.Join(_tyeFolderPath, GetStoreFile(storeName)); |
|||
|
|||
if (!File.Exists(filePath)) |
|||
{ |
|||
return Array.Empty<IDictionary<string, string>>(); |
|||
} |
|||
|
|||
var contents = await File.ReadAllTextAsync(filePath); |
|||
var events = contents.Split(Environment.NewLine); |
|||
|
|||
return events.Where(e => !string.IsNullOrEmpty(e.Trim())) |
|||
.Select(e => JsonSerializer.Deserialize<IDictionary<string, string>>(e)) |
|||
.ToList(); |
|||
} |
|||
|
|||
private SemaphoreSlim GetSempahoreForStore(string storeName) |
|||
{ |
|||
return _fileWriteSemaphores.GetOrAdd(storeName, _ => new SemaphoreSlim(1, 1)); |
|||
} |
|||
|
|||
private string GetStoreFile(string storeName) => $"{storeName}_store"; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
Directory.Delete(_tyeFolderPath, true); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,155 @@ |
|||
// Licensed to the .NET Foundation under one or more agreements.
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
|||
// See the LICENSE file in the project root for more information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.CommandLine; |
|||
using System.Diagnostics; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Microsoft.Tye; |
|||
using Microsoft.Tye.ConfigModel; |
|||
using Microsoft.Tye.Hosting; |
|||
using Microsoft.Tye.Hosting.Model; |
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
namespace E2ETest |
|||
{ |
|||
public class TyePurgeTests |
|||
{ |
|||
private readonly ITestOutputHelper output; |
|||
private readonly TestOutputLogEventSink sink; |
|||
|
|||
public TyePurgeTests(ITestOutputHelper output) |
|||
{ |
|||
this.output = output; |
|||
sink = new TestOutputLogEventSink(output); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task FrontendBackendPurgeTest() |
|||
{ |
|||
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "frontend-backend")); |
|||
using var tempDirectory = TempDirectory.Create(); |
|||
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath); |
|||
|
|||
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml")); |
|||
var tyeDir = new DirectoryInfo(Path.Combine(tempDirectory.DirectoryPath, ".tye")); |
|||
var outputContext = new OutputContext(sink, Verbosity.Debug); |
|||
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); |
|||
var host = new TyeHost(application.ToHostingApplication(), Array.Empty<string>()) |
|||
{ |
|||
Sink = sink, |
|||
}; |
|||
|
|||
try |
|||
{ |
|||
await TestHelpers.StartHostAndWaitForReplicasToStart(host); |
|||
try |
|||
{ |
|||
var pids = GetAllAppPids(host.Application); |
|||
|
|||
Assert.True(Directory.Exists(tyeDir.FullName)); |
|||
Assert.Subset(new HashSet<int>(GetAllPids()), new HashSet<int>(pids)); |
|||
|
|||
await TestHelpers.PurgeHostAndWaitForGivenReplicasToStop(host, |
|||
GetAllReplicasNames(host.Application), tyeDir.FullName); |
|||
|
|||
var runningPids = new HashSet<int>(GetAllPids()); |
|||
Assert.True(pids.All(pid => !runningPids.Contains(pid))); |
|||
} |
|||
finally |
|||
{ |
|||
await host.StopAsync(); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
host.Dispose(); |
|||
Assert.False(Directory.Exists(tyeDir.FullName)); |
|||
} |
|||
} |
|||
|
|||
[ConditionalFact] |
|||
[SkipIfDockerNotRunning] |
|||
public async Task MultiProjectPurgeTest() |
|||
{ |
|||
var projectDirectory = new DirectoryInfo(Path.Combine(TestHelpers.GetSolutionRootDirectory("tye"), "samples", "multi-project")); |
|||
using var tempDirectory = TempDirectory.Create(); |
|||
DirectoryCopy.Copy(projectDirectory.FullName, tempDirectory.DirectoryPath); |
|||
|
|||
var projectFile = new FileInfo(Path.Combine(tempDirectory.DirectoryPath, "tye.yaml")); |
|||
var tyeDir = new DirectoryInfo(Path.Combine(tempDirectory.DirectoryPath, ".tye")); |
|||
var outputContext = new OutputContext(sink, Verbosity.Debug); |
|||
var application = await ApplicationFactory.CreateAsync(outputContext, projectFile); |
|||
var host = new TyeHost(application.ToHostingApplication(), Array.Empty<string>()) |
|||
{ |
|||
Sink = sink, |
|||
}; |
|||
|
|||
try |
|||
{ |
|||
await TestHelpers.StartHostAndWaitForReplicasToStart(host); |
|||
try |
|||
{ |
|||
var pids = GetAllAppPids(host.Application); |
|||
var containers = GetAllContainerIds(host.Application); |
|||
|
|||
Assert.True(Directory.Exists(tyeDir.FullName)); |
|||
Assert.Subset(new HashSet<int>(GetAllPids()), new HashSet<int>(pids)); |
|||
Assert.Subset(new HashSet<string>(await DockerAssert.GetRunningContainersIdsAsync(output)), |
|||
new HashSet<string>(containers)); |
|||
|
|||
await TestHelpers.PurgeHostAndWaitForGivenReplicasToStop(host, |
|||
GetAllReplicasNames(host.Application), tyeDir.FullName); |
|||
|
|||
var runningPids = new HashSet<int>(GetAllPids()); |
|||
Assert.True(pids.All(pid => !runningPids.Contains(pid))); |
|||
var runningContainers = |
|||
new HashSet<string>(await DockerAssert.GetRunningContainersIdsAsync(output)); |
|||
Assert.True(containers.All(c => !runningContainers.Contains(c))); |
|||
} |
|||
finally |
|||
{ |
|||
await host.StopAsync(); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
host.Dispose(); |
|||
Assert.False(Directory.Exists(tyeDir.FullName)); |
|||
} |
|||
} |
|||
|
|||
private string[] GetAllReplicasNames(Microsoft.Tye.Hosting.Model.Application application) |
|||
{ |
|||
var replicas = application.Services.SelectMany(s => s.Value.Replicas); |
|||
return replicas.Select(r => r.Value.Name).ToArray(); |
|||
} |
|||
|
|||
private int[] GetAllAppPids(Microsoft.Tye.Hosting.Model.Application application) |
|||
{ |
|||
var replicas = application.Services.SelectMany(s => s.Value.Replicas); |
|||
var ids = replicas.Where(r => r.Value is ProcessStatus).Select(r => ((ProcessStatus)r.Value).Pid ?? -1).ToArray(); |
|||
|
|||
return ids; |
|||
} |
|||
|
|||
private string[] GetAllContainerIds(Microsoft.Tye.Hosting.Model.Application application) |
|||
{ |
|||
var replicas = application.Services.SelectMany(s => s.Value.Replicas); |
|||
var ids = replicas.Where(r => r.Value is DockerStatus).Select(r => ((DockerStatus)r.Value).ContainerId!).ToArray(); |
|||
|
|||
return ids; |
|||
} |
|||
|
|||
private int[] GetAllPids() |
|||
{ |
|||
return Process.GetProcesses().Select(p => p.Id).ToArray(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue