Skip to content

Commit

Permalink
Fix Tests that start Tentacle as a Service (#741)
Browse files Browse the repository at this point in the history
Set Environment variables for the Windows and Linux Tentacle Services in the tests
  • Loading branch information
nathanwoctopusdeploy authored Dec 14, 2023
1 parent be322a0 commit 70c2052
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 58 deletions.
118 changes: 62 additions & 56 deletions source/Octopus.Tentacle.Tests.Integration/Support/TentacleBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Autofac;
using CliWrap;
using CliWrap.Exceptions;
using Microsoft.Win32;
using Nito.AsyncEx.Interop;
using NUnit.Framework;
using Octopus.Tentacle.CommonTestUtils;
using Octopus.Tentacle.Configuration;
using Octopus.Tentacle.Tests.Integration.Util;
using Serilog;
Expand Down Expand Up @@ -153,9 +157,10 @@ protected async Task<RunningTentacle> StartTentacle(
var serviceInstalled = false;
var serviceStarted = false;

await RunTentacleCommandOutOfProcess(
await RunCommandOutOfProcess(
tentacleExe,
new[] {"service", "--install", $"--instance={instanceName}"},
"Tentacle",
tempDirectory,
s =>
{
Expand All @@ -167,15 +172,18 @@ await RunTentacleCommandOutOfProcess(
runTentacleEnvironmentVariables,
logger,
cancellationToken);

if (!serviceInstalled)
{
throw new Exception("Failed to install service");
}

await RunTentacleCommandOutOfProcess(
await SetEnvironmentVariablesForService(instanceName, tempDirectory, logger, cancellationToken);

await RunCommandOutOfProcess(
tentacleExe,
new[] { "service", "--start", $"--instance={instanceName}" },
"Tentacle",
tempDirectory,
s =>
{
Expand All @@ -193,11 +201,8 @@ await RunTentacleCommandOutOfProcess(
throw new Exception("Failed to start service");
}

using var timeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(20));
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellationTokenSource.Token);

logger.Information("Waiting for the Tentacle Service to start up and assign a Listening Port / no port if Polling");
var tentacleState = await WaitForTentacleToStart(tempDirectory, linkedCancellationTokenSource.Token);
var tentacleState = await WaitForTentacleToStart(tempDirectory, cancellationToken);

if (tentacleState.Started)
{
Expand All @@ -206,52 +211,15 @@ await RunTentacleCommandOutOfProcess(
}
else
{
logger.Warning("The Tentacle failed to start correctly. Trying Again. Last Log File Content");
logger.Warning(tentacleState.LogContent);

File.Delete(Path.Combine(tempDirectory.DirectoryPath, "Logs", "OctopusTentacle.txt"));

await RunTentacleCommandOutOfProcess(
tentacleExe,
new[] { "service", "--stop", $"--instance={instanceName}" },
tempDirectory,
s =>
{ },
runTentacleEnvironmentVariables,
logger,
cancellationToken);

File.Delete(Path.Combine(tempDirectory.DirectoryPath, "Logs", "OctopusTentacle.txt"));

await RunTentacleCommandOutOfProcess(
tentacleExe,
new[] { "service", "--start", $"--instance={instanceName}" },
tempDirectory,
s =>
{ },
runTentacleEnvironmentVariables,
logger,
cancellationToken);

tentacleState = await WaitForTentacleToStart(tempDirectory, cancellationToken);

if (tentacleState.Started)
{
listeningPort = tentacleState.ListeningPort;
hasTentacleStarted.Set();
}
else
{
logger.Error("The Tentacle failed to start correctly. Last Log File Content");
logger.Error(tentacleState.LogContent);
}
}
logger.Warning("The Tentacle failed to start correctly.");
logger.Warning(tentacleState.LogContent); }
}
else
{
await RunTentacleCommandOutOfProcess(
await RunCommandOutOfProcess(
tentacleExe,
new[] {"agent", $"--instance={instanceName}", "--noninteractive"},
"Tentacle",
tempDirectory,
s =>
{
Expand Down Expand Up @@ -297,6 +265,40 @@ await RunTentacleCommandOutOfProcess(
return (runningTentacle, serviceUri);
}

async Task SetEnvironmentVariablesForService(string instanceName, TemporaryDirectory tempDirectory, ILogger logger, CancellationToken cancellationToken)
{
if (PlatformDetection.IsRunningOnWindows)
{
var environment = runTentacleEnvironmentVariables.Select(x => $"{x.Key}={x.Value}").ToArray();

#pragma warning disable CA1416
Registry.LocalMachine.OpenSubKey($"SYSTEM\\CurrentControlSet\\Services\\OctopusDeploy Tentacle: {instanceName}", RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.SetValue)
.SetValue("Environment", environment, RegistryValueKind.MultiString);
#pragma warning restore CA1416
}
else
{
var environment = string.Join("", runTentacleEnvironmentVariables.Select(x => $"Environment={x.Key}={x.Value}{Environment.NewLine}"));

var systemdDirectoryInfo = new DirectoryInfo("/etc/systemd/system");
var serviceFileInfo = systemdDirectoryInfo.GetFiles($"{instanceName}.service").Single();

var service = await File.ReadAllTextAsync(serviceFileInfo.FullName, cancellationToken);
service = service.Replace("[Service]", $"[Service]{Environment.NewLine}{environment}{Environment.NewLine}");
await File.WriteAllTextAsync(serviceFileInfo.FullName, service, cancellationToken);

await RunCommandOutOfProcess(
"systemctl",
new[] { "daemon-reload" },
"systemctl",
tempDirectory,
_ => { },
new Dictionary<string, string>(),
logger,
cancellationToken);
}
}

static async Task<(bool Started, int? ListeningPort, string LogContent)> WaitForTentacleToStart(TemporaryDirectory tempDirectory, CancellationToken localCancellationToken)
{
var lastLogFileContents = string.Empty;
Expand Down Expand Up @@ -347,18 +349,20 @@ internal async Task DeleteInstanceIgnoringFailure(bool runningAsService, string
{
try
{
await RunTentacleCommandOutOfProcess(
await RunCommandOutOfProcess(
tentacleExe,
new[] { "service", $"--instance={instanceName}", "--stop" },
"Tentacle",
tmp,
s => {},
runTentacleEnvironmentVariables,
logger,
cancellationToken);

await RunTentacleCommandOutOfProcess(
await RunCommandOutOfProcess(
tentacleExe,
new[] {"service", "--uninstall", $"--instance={instanceName}"},
"Tentacle",
tmp,
s => {},
runTentacleEnvironmentVariables,
Expand Down Expand Up @@ -396,19 +400,21 @@ internal string InstanceNameGenerator()

async Task RunTentacleCommand(string tentacleExe, string[] args, TemporaryDirectory tmp, ILogger logger, CancellationToken cancellationToken)
{
await RunTentacleCommandOutOfProcess(
await RunCommandOutOfProcess(
tentacleExe,
args,
"Tentacle",
tmp,
_ => { },
runTentacleEnvironmentVariables,
logger,
cancellationToken);
}

async Task RunTentacleCommandOutOfProcess(
string tentacleExe,
async Task RunCommandOutOfProcess(
string targetFilePath,
string[] args,
string commandName,
TemporaryDirectory tmp,
Action<string> commandOutput,
IReadOnlyDictionary<string, string> environmentVariables,
Expand All @@ -418,14 +424,14 @@ async Task RunTentacleCommandOutOfProcess(
async Task ProcessLogs(string s, CancellationToken ct)
{
await Task.CompletedTask;
logger.Information("[Tentacle] " + s);
logger.Information($"[{commandName}] " + s);
commandOutput(s);
}

try
{
var commandResult = await RetryHelper.RetryAsync<CommandResult, CommandExecutionException>(
() => Cli.Wrap(tentacleExe)
() => Cli.Wrap(targetFilePath)
.WithArguments(args)
.WithEnvironmentVariables(environmentVariables)
.WithWorkingDirectory(tmp.DirectoryPath)
Expand All @@ -437,7 +443,7 @@ async Task ProcessLogs(string s, CancellationToken ct)

if (commandResult.ExitCode != 0)
{
throw new Exception("Tentacle returns non zero exit code: " + commandResult.ExitCode);
throw new Exception($"{commandName} returns non zero exit code: " + commandResult.ExitCode);
}
}
catch (OperationCanceledException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ public ApplicationInstanceStore(
else
{
machineConfigurationHomeDirectory = PlatformDetection.IsRunningOnWindows ?
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Octopus") :
"/etc/octopus";
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Octopus") :
"/etc/octopus";
}

log.Verbose("Machine configuration home directory is " + machineConfigurationHomeDirectory);
}

public ApplicationInstanceRecord LoadInstanceDetails(string? instanceName)
Expand Down

0 comments on commit 70c2052

Please sign in to comment.