Skip to content

Commit

Permalink
Merge pull request #1763 from tgstation/PortInUseDiagnosis [TGSDeploy]
Browse files Browse the repository at this point in the history
Log warnings as to why ports cannot be allocated. Fix DME's with spaces
  • Loading branch information
Cyberboss authored Jan 7, 2024
2 parents 2c986de + 86dab93 commit 005a195
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 20 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ jobs:
if hash DreamMaker 2>/dev/null
then
DreamMaker tests/DMAPI/BasicOperation/basic_operation_test.dme 2>&1 | tee result.log
DreamMaker "tests/DMAPI/BasicOperation/basic operation_test.dme" 2>&1 | tee result.log
retval=$?
if ! grep '\- 0 errors, 0 warnings' result.log
then
Expand Down Expand Up @@ -205,7 +205,7 @@ jobs:
- name: Build DMAPI
run: |
cd tests/DMAPI/BasicOperation
$HOME/OpenDream/tgs_deploy/bin/compiler/DMCompiler --verbose --notices-enabled basic_operation_test.dme
$HOME/OpenDream/tgs_deploy/bin/compiler/DMCompiler --verbose --notices-enabled "basic operation_test.dme"
pages-build:
name: Build gh-pages
Expand Down
2 changes: 1 addition & 1 deletion build/Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- Integration tests will ensure they match across the board -->
<Import Project="WebpanelVersion.props" />
<PropertyGroup>
<TgsCoreVersion>6.1.1</TgsCoreVersion>
<TgsCoreVersion>6.1.2</TgsCoreVersion>
<TgsConfigVersion>5.0.0</TgsConfigVersion>
<TgsApiVersion>10.0.0</TgsApiVersion>
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ await eventConsumer.HandleEvent(
throw new JobException(ErrorCode.DeploymentMissingDme);
}

logger.LogDebug("Selected {dmeName}.dme for compilation!", job.DmeName);
logger.LogDebug("Selected \"{dmeName}.dme\" for compilation!", job.DmeName);

progressReporter.StageName = "Modifying .dme";
await ModifyDme(job, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public override string FormatServerArguments(

var arguments = String.Format(
CultureInfo.InvariantCulture,
"{0} -port {1} -ports 1-65535 {2}-close -verbose -{3} -{4}{5}{6}{7} -params \"{8}\"",
"\"{0}\" -port {1} -ports 1-65535 {2}-close -verbose -{3} -{4}{5}{6}{7} -params \"{8}\"",
dmbProvider.DmbName,
launchParameters.Port!.Value,
launchParameters.AllowWebClient!.Value
Expand Down
2 changes: 1 addition & 1 deletion src/Tgstation.Server.Host/Core/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ void AddTypedContext<TContext>()
services.AddSingleton<ISwarmServiceController>(x => x.GetRequiredService<SwarmService>());

// configure component services
services.AddScoped<IPortAllocator, PortAllocator>();
services.AddSingleton<IPortAllocator, PortAllocator>();
services.AddSingleton<IInstanceFactory, InstanceFactory>();
services.AddSingleton<IGitRemoteFeaturesFactory, GitRemoteFeaturesFactory>();
services.AddSingleton<ILibGit2RepositoryFactory, LibGit2RepositoryFactory>();
Expand Down
81 changes: 70 additions & 11 deletions src/Tgstation.Server.Host/Utils/PortAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
namespace Tgstation.Server.Host.Utils
{
/// <inheritdoc />
sealed class PortAllocator : IPortAllocator
sealed class PortAllocator : IPortAllocator, IDisposable
{
/// <summary>
/// The <see cref="IServerPortProvider"/> for the <see cref="PortAllocator"/>.
Expand All @@ -27,7 +27,7 @@ sealed class PortAllocator : IPortAllocator
/// <summary>
/// The <see cref="IDatabaseContext"/> for the <see cref="PortAllocator"/>.
/// </summary>
readonly IDatabaseContext databaseContext;
readonly IDatabaseContextFactory databaseContextFactory;

/// <summary>
/// The <see cref="IPlatformIdentifier"/> for the <see cref="PortAllocator"/>.
Expand All @@ -44,45 +44,79 @@ sealed class PortAllocator : IPortAllocator
/// </summary>
readonly SwarmConfiguration swarmConfiguration;

/// <summary>
/// The <see cref="SemaphoreSlim"/> used to serialized port requisition requests.
/// </summary>
readonly SemaphoreSlim allocatorLock;

/// <summary>
/// Initializes a new instance of the <see cref="PortAllocator"/> class.
/// </summary>
/// <param name="serverPortProvider">The value of <see cref="serverPortProvider"/>.</param>
/// <param name="databaseContext">The value of <see cref="databaseContext"/>.</param>
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/>.</param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
/// <param name="swarmConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="swarmConfiguration"/>.</param>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public PortAllocator(
IServerPortProvider serverPortProvider,
IDatabaseContext databaseContext,
IDatabaseContextFactory databaseContextFactory,
IPlatformIdentifier platformIdentifier,
IOptions<SwarmConfiguration> swarmConfigurationOptions,
ILogger<PortAllocator> logger)
{
this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider));
this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));

allocatorLock = new SemaphoreSlim(1);
}

/// <inheritdoc />
public void Dispose() => allocatorLock.Dispose();

/// <inheritdoc />
public async ValueTask<ushort?> GetAvailablePort(ushort basePort, bool checkOne, CancellationToken cancellationToken)
{
logger.LogTrace("Port allocation >= {basePort} requested...", basePort);
ushort? result = null;
using (await SemaphoreSlimContext.Lock(allocatorLock, cancellationToken))
await databaseContextFactory.UseContext(
async databaseContext => result = await GetAvailablePort(databaseContext, basePort, checkOne, cancellationToken));
return result;
}

/// <summary>
/// Gets a port not currently in use by TGS.
/// </summary>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to use.</param>
/// <param name="basePort">The port to check first. Will not allocate a port lower than this.</param>
/// <param name="checkOne">If only <paramref name="basePort"/> should be checked and no others.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the first available port on success, <see langword="null"/> on failure.</returns>
async ValueTask<ushort?> GetAvailablePort(IDatabaseContext databaseContext, ushort basePort, bool checkOne, CancellationToken cancellationToken)
{
logger.LogTrace("Port allocation >= {basePort} requested...", basePort);
var ddPorts = await databaseContext
.DreamDaemonSettings
.AsQueryable()
.Where(x => x.Instance!.SwarmIdentifer == swarmConfiguration.Identifier)
.Select(x => x.Port)
.Select(x => new
{
Port = x.Port!.Value,
x.InstanceId,
})
.ToListAsync(cancellationToken);

var dmPorts = await databaseContext
.DreamMakerSettings
.AsQueryable()
.Where(x => x.Instance!.SwarmIdentifer == swarmConfiguration.Identifier)
.Select(x => x.ApiValidationPort)
.Select(x => new
{
ApiValidationPort = x.ApiValidationPort!.Value,
x.InstanceId,
})
.ToListAsync(cancellationToken);

var exceptions = new List<Exception>();
Expand All @@ -94,10 +128,35 @@ public PortAllocator(
if (checkOne && port != basePort)
break;

if (port == serverPortProvider.HttpApiPort
|| ddPorts.Contains(port)
|| dmPorts.Contains(port))
if (port == serverPortProvider.HttpApiPort)
{
logger.LogWarning("Cannot allocate port {port} as it is the TGS API port!", port);
continue;
}

var reservedGamePortData = ddPorts.Where(data => data.Port == port).ToList();
if (reservedGamePortData.Count > 0)
{
logger.LogWarning(
"Cannot allocate port {port} as it in use by the game server of instance(s): {instanceId}!",
port,
String.Join(
", ",
reservedGamePortData.Select(data => data.InstanceId)));
continue;
}

var reservedApiValidationPortData = dmPorts.Where(data => data.ApiValidationPort == port).ToList();
if (reservedApiValidationPortData.Count > 0)
{
logger.LogWarning(
"Cannot allocate port {port} as it in use by the API validation server of instance(s): {instanceId}!",
port,
String.Join(
", ",
reservedApiValidationPortData.Select(data => data.InstanceId)));
continue;
}

try
{
Expand Down
4 changes: 2 additions & 2 deletions tests/Tgstation.Server.Tests/Live/Instance/WatchdogTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ async Task TestDMApiFreeDeploy(CancellationToken cancellationToken)
var initialCompileJob = daemonStatus.ActiveCompileJob;
await CheckDMApiFail(daemonStatus.ActiveCompileJob, cancellationToken);

daemonStatus = await DeployTestDme("BasicOperation/basic_operation_test", DreamDaemonSecurity.Trusted, true, cancellationToken);
daemonStatus = await DeployTestDme("BasicOperation/basic operation_test", DreamDaemonSecurity.Trusted, true, cancellationToken);

Assert.AreEqual(WatchdogStatus.Online, daemonStatus.Status.Value);
Assert.AreEqual(false, daemonStatus.SoftRestart); // dme name change triggered, instant reboot
Expand All @@ -629,7 +629,7 @@ async Task RunBasicTest(CancellationToken cancellationToken)
AdditionalParameters = "test=bababooey"
}, cancellationToken);
Assert.AreEqual("test=bababooey", daemonStatus.AdditionalParameters);
daemonStatus = await DeployTestDme("BasicOperation/basic_operation_test", DreamDaemonSecurity.Trusted, true, cancellationToken);
daemonStatus = await DeployTestDme("BasicOperation/basic operation_test", DreamDaemonSecurity.Trusted, true, cancellationToken);

Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value);
Assert.IsNotNull(daemonStatus.ActiveCompileJob);
Expand Down
2 changes: 1 addition & 1 deletion tgstation-server.sln
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiFree", "ApiFree", "{7B8F
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BasicOperation", "BasicOperation", "{F32B9514-AAD9-429D-841A-ED810FC2598C}"
ProjectSection(SolutionItems) = preProject
tests\DMAPI\BasicOperation\basic_operation_test.dme = tests\DMAPI\BasicOperation\basic_operation_test.dme
tests\DMAPI\BasicOperation\Config.dm = tests\DMAPI\BasicOperation\Config.dm
tests\DMAPI\BasicOperation\basic operation_test.dme = tests\DMAPI\BasicOperation\basic operation_test.dme
tests\DMAPI\BasicOperation\Test.dm = tests\DMAPI\BasicOperation\Test.dm
EndProjectSection
EndProject
Expand Down

0 comments on commit 005a195

Please sign in to comment.