Skip to content

Commit

Permalink
Update Kubernetes Client package to v15 (#1051)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevjt authored Dec 19, 2024
1 parent 266b088 commit 24df9db
Show file tree
Hide file tree
Showing 15 changed files with 359 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
using Halibut;
using Halibut.Diagnostics;
using Halibut.Diagnostics.LogCreators;
using Halibut.Logging;
using System;
using Halibut;
using Octopus.Tentacle.Client;
using Octopus.Tentacle.Client.Retries;
using Octopus.Tentacle.Client.Scripts;
using Octopus.Tentacle.CommonTestUtils;
using Octopus.Tentacle.Contracts.Observability;
using Octopus.Tentacle.Kubernetes.Tests.Integration.Setup;
using Octopus.Tentacle.Kubernetes.Tests.Integration.Tooling;
using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators;
using Octopus.Tentacle.Tests.Integration.Common.Logging;

namespace Octopus.Tentacle.Kubernetes.Tests.Integration;
namespace Octopus.Tentacle.Kubernetes.Tests.Integration.KubernetesAgent;

public abstract class KubernetesAgentIntegrationTest
{
Expand All @@ -23,9 +18,7 @@ public abstract class KubernetesAgentIntegrationTest
protected ILogger? Logger { get; private set; }

protected KubernetesAgentInstaller KubernetesAgentInstaller => kubernetesAgentInstaller ?? throw new InvalidOperationException("Expected kubernetesAgentInstaller to be set");

protected HalibutRuntime ServerHalibutRuntime { get; private set; } = null!;


protected TentacleClient TentacleClient { get; private set; } = null!;

protected CancellationToken CancellationToken { get; private set; }
Expand All @@ -34,6 +27,8 @@ public abstract class KubernetesAgentIntegrationTest

protected readonly IDictionary<string, string> CustomHelmValues = new Dictionary<string, string>();

HalibutRuntime serverHalibutRuntime;

string? agentThumbprint;

[OneTimeSetUp]
Expand All @@ -54,12 +49,13 @@ public async Task OneTimeSetUp()
KubernetesTestsGlobalContext.Instance.Logger);

//create a new server halibut runtime
var listeningPort = BuildServerHalibutRuntimeAndListen();
serverHalibutRuntime = SetupHelpers.BuildServerHalibutRuntime();
var listeningPort = serverHalibutRuntime.Listen();

agentThumbprint = await kubernetesAgentInstaller.InstallAgent(listeningPort, KubernetesTestsGlobalContext.Instance.TentacleImageAndTag, CustomHelmValues);

//trust the generated cert thumbprint
ServerHalibutRuntime.Trust(agentThumbprint);
serverHalibutRuntime.Trust(agentThumbprint);
}

[SetUp]
Expand All @@ -76,7 +72,7 @@ public void SetUp()
CancellationToken = cancellationTokenSource.Token;

//each test should get its own tentacle client, so it gets its own builders
BuildTentacleClient();
TentacleClient = SetupHelpers.BuildTentacleClient(KubernetesAgentInstaller.SubscriptionId, agentThumbprint, serverHalibutRuntime, ConfigureTentacleServiceDecoratorBuilder);
}

[TearDown]
Expand All @@ -91,45 +87,14 @@ public async Task TearDown()
cancellationTokenSource?.Dispose();
}

protected virtual TentacleServiceDecoratorBuilder ConfigureTentacleServiceDecoratorBuilder(TentacleServiceDecoratorBuilder builder) => builder;

void BuildTentacleClient()
{
var endpoint = new ServiceEndPoint(KubernetesAgentInstaller.SubscriptionId, agentThumbprint, ServerHalibutRuntime.TimeoutsAndLimits);

var retrySettings = new RpcRetrySettings(true, TimeSpan.FromMinutes(2));
var clientOptions = new TentacleClientOptions(retrySettings);

TentacleClient.CacheServiceWasNotFoundResponseMessages(ServerHalibutRuntime);

var builder = new TentacleServiceDecoratorBuilder();
ConfigureTentacleServiceDecoratorBuilder(builder);

TentacleClient = new TentacleClient(
endpoint,
ServerHalibutRuntime,
new PollingTentacleScriptObserverBackoffStrategy(),
new NoTentacleClientObserver(),
clientOptions,
builder.Build());
}

int BuildServerHalibutRuntimeAndListen()
protected virtual void ConfigureTentacleServiceDecoratorBuilder(TentacleServiceDecoratorBuilder builder)
{
var serverHalibutRuntimeBuilder = new HalibutRuntimeBuilder()
.WithServerCertificate(TestCertificates.Server)
.WithHalibutTimeoutsAndLimits(HalibutTimeoutsAndLimits.RecommendedValues())
.WithLogFactory(new TestContextLogCreator("Server", LogLevel.Trace).ToCachingLogFactory());

ServerHalibutRuntime = serverHalibutRuntimeBuilder.Build();

return ServerHalibutRuntime.Listen();
}

[OneTimeTearDown]
public async Task OneTimeTearDown()
{
await ServerHalibutRuntime.DisposeAsync();
await serverHalibutRuntime.DisposeAsync();
kubernetesAgentInstaller?.Dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using Octopus.Tentacle.Diagnostics;
using Octopus.Tentacle.Kubernetes.Diagnostics;

namespace Octopus.Tentacle.Kubernetes.Tests.Integration;
namespace Octopus.Tentacle.Kubernetes.Tests.Integration.KubernetesAgent;

public class KubernetesAgentMetricsIntegrationTest : KubernetesAgentIntegrationTest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using Octopus.Tentacle.Kubernetes.Tests.Integration.Setup;

namespace Octopus.Tentacle.Kubernetes.Tests.Integration.KubernetesAgent;

[SetUpFixture]
public class KubernetesClusterOneTimeSetUp
{
KubernetesClusterInstaller? installer;

[OneTimeSetUp]
public async Task OneTimeSetUp()
{
var toolDownloader = new RequiredToolDownloader(KubernetesTestsGlobalContext.Instance.TemporaryDirectory, KubernetesTestsGlobalContext.Instance.Logger);
var (kindExePath, helmExePath, kubeCtlPath) = await toolDownloader.DownloadRequiredTools(CancellationToken.None);

installer = new KubernetesClusterInstaller(KubernetesTestsGlobalContext.Instance.TemporaryDirectory, kindExePath, helmExePath, kubeCtlPath, KubernetesTestsGlobalContext.Instance.Logger);
await installer.InstallLatestSupported();

KubernetesTestsGlobalContext.Instance.TentacleImageAndTag = SetupHelpers.GetTentacleImageAndTag(kindExePath, installer);
KubernetesTestsGlobalContext.Instance.SetToolExePaths(helmExePath, kubeCtlPath);
KubernetesTestsGlobalContext.Instance.KubeConfigPath = installer.KubeConfigPath;
}

[OneTimeTearDown]
public void OneTimeTearDown()
{
installer?.Dispose();
KubernetesTestsGlobalContext.Instance.Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
using System;
using FluentAssertions;
using FluentAssertions.Execution;
using Octopus.Client.Model.Endpoints;
using Octopus.Tentacle.Client.Scripts.Models;
using Octopus.Tentacle.Client.Scripts.Models.Builders;
using Octopus.Tentacle.CommonTestUtils;
using Octopus.Tentacle.CommonTestUtils.Diagnostics;
using Octopus.Tentacle.Contracts;
using Octopus.Tentacle.Contracts.Capabilities;
using Octopus.Tentacle.Contracts.ClientServices;
using Octopus.Tentacle.Contracts.KubernetesScriptServiceV1;
using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators;
using Octopus.Tentacle.Kubernetes.Tests.Integration.Util;
using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators;
using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators.Proxies;

namespace Octopus.Tentacle.Kubernetes.Tests.Integration;
namespace Octopus.Tentacle.Kubernetes.Tests.Integration.KubernetesAgent;

[TestFixture]
public class KubernetesScriptServiceV1IntegrationTest : KubernetesAgentIntegrationTest
{
IRecordedMethodUsages recordedMethodUsages = null!;

protected override TentacleServiceDecoratorBuilder ConfigureTentacleServiceDecoratorBuilder(TentacleServiceDecoratorBuilder builder)
protected override void ConfigureTentacleServiceDecoratorBuilder(TentacleServiceDecoratorBuilder builder)
{
builder.RecordMethodUsages<IAsyncClientKubernetesScriptServiceV1>(out var recordedUsages);

recordedMethodUsages = recordedUsages;

return builder;
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using System;
using FluentAssertions;
using Halibut;
using Octopus.Tentacle.Client;
using Octopus.Tentacle.Client.Scripts.Models;
using Octopus.Tentacle.Client.Scripts.Models.Builders;
using Octopus.Tentacle.CommonTestUtils;
using Octopus.Tentacle.CommonTestUtils.Diagnostics;
using Octopus.Tentacle.Contracts;
using Octopus.Tentacle.Contracts.ClientServices;
using Octopus.Tentacle.Kubernetes.Tests.Integration.Setup;
using Octopus.Tentacle.Kubernetes.Tests.Integration.Util;
using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators;
using Octopus.Tentacle.Tests.Integration.Common.Builders.Decorators.Proxies;
using Octopus.Tentacle.Tests.Integration.Common.Logging;

namespace Octopus.Tentacle.Kubernetes.Tests.Integration;

[TestFixture]
public class KubernetesClientCompatibilityTests
{
static readonly object[] TestClusterVersions =
[
new object[] {new ClusterVersion(1, 31)},
new object[] {new ClusterVersion(1, 30)},
new object[] {new ClusterVersion(1, 29)},
new object[] {new ClusterVersion(1, 28)},
];

KubernetesTestsGlobalContext? testContext;
ILogger logger = null!;
TemporaryDirectory toolsTemporaryDirectory;
string kindExePath;
string helmExePath;
string kubeCtlPath;
KubernetesClusterInstaller? clusterInstaller;
KubernetesAgentInstaller? kubernetesAgentInstaller;
HalibutRuntime serverHalibutRuntime = null!;
string? agentThumbprint;
TraceLogFileLogger? traceLogFileLogger;
CancellationToken cancellationToken;
CancellationTokenSource? cancellationTokenSource;
TentacleClient tentacleClient = null!;
IRecordedMethodUsages recordedMethodUsages = null!;

[OneTimeSetUp]
public async Task OneTimeSetup()
{
logger = new SerilogLoggerBuilder().Build();
toolsTemporaryDirectory = new TemporaryDirectory();
var toolDownloader = new RequiredToolDownloader(toolsTemporaryDirectory, logger);
(kindExePath, helmExePath, kubeCtlPath) = await toolDownloader.DownloadRequiredTools(CancellationToken.None);
}

[OneTimeTearDown]
public void OneTimeTearDown()
{
toolsTemporaryDirectory.Dispose();
}

[TearDown]
public async Task TearDown()
{
if (traceLogFileLogger is not null) await traceLogFileLogger.DisposeAsync();
if (cancellationTokenSource is not null)
{
await cancellationTokenSource.CancelAsync();
cancellationTokenSource.Dispose();
}
clusterInstaller?.Dispose();
testContext?.Dispose();

traceLogFileLogger = null;
cancellationTokenSource = null;
clusterInstaller = null;
testContext = null;
}

[Test]
[TestCaseSource(nameof(TestClusterVersions))]
public async Task RunSimpleScript(ClusterVersion clusterVersion)
{
await SetUp(clusterVersion);

// Arrange
var logs = new List<ProcessOutput>();
var scriptCompleted = false;

var builder = new ExecuteKubernetesScriptCommandBuilder(LoggingUtils.CurrentTestHash())
.WithScriptBody(script => script
.Print("Hello World")
.PrintNTimesWithDelay("Yep", 30, TimeSpan.FromMilliseconds(100)));

var command = builder.Build();

// Act
var result = await tentacleClient.ExecuteScript(command, StatusReceived, ScriptCompleted, new InMemoryLog(), cancellationToken);

// Assert
logs.Should().Contain(po => po.Text.StartsWith("[POD EVENT]")); // Verify that we are receiving some pod events
logs.Should().Contain(po => po.Source == ProcessOutputSource.StdOut && po.Text == "Hello World");
scriptCompleted.Should().BeTrue();
result.ExitCode.Should().Be(0);
result.State.Should().Be(ProcessState.Complete);

recordedMethodUsages.For(nameof(IAsyncClientKubernetesScriptServiceV1.StartScriptAsync)).Started.Should().Be(1);
recordedMethodUsages.For(nameof(IAsyncClientKubernetesScriptServiceV1.GetStatusAsync)).Started.Should().BeGreaterThan(1);
recordedMethodUsages.For(nameof(IAsyncClientKubernetesScriptServiceV1.CompleteScriptAsync)).Started.Should().Be(1);
recordedMethodUsages.For(nameof(IAsyncClientKubernetesScriptServiceV1.CancelScriptAsync)).Started.Should().Be(0);

return;

void StatusReceived(ScriptExecutionStatus status)
{
logs.AddRange(status.Logs);
}

Task ScriptCompleted(CancellationToken ct)
{
scriptCompleted = true;
return Task.CompletedTask;
}
}

async Task SetUp(ClusterVersion clusterVersion)
{
testContext = new KubernetesTestsGlobalContext(logger);

await SetupCluster(clusterVersion);

kubernetesAgentInstaller = new KubernetesAgentInstaller(
testContext.TemporaryDirectory,
testContext.HelmExePath,
testContext.KubeCtlExePath,
testContext.KubeConfigPath,
testContext.Logger);

//create a new server halibut runtime
serverHalibutRuntime = SetupHelpers.BuildServerHalibutRuntime();
var listeningPort = serverHalibutRuntime.Listen();

agentThumbprint = await kubernetesAgentInstaller.InstallAgent(listeningPort, testContext.TentacleImageAndTag, new Dictionary<string, string>());

//trust the generated cert thumbprint
serverHalibutRuntime.Trust(agentThumbprint);

traceLogFileLogger = new TraceLogFileLogger(LoggingUtils.CurrentTestHash());
logger = new SerilogLoggerBuilder()
.SetTraceLogFileLogger(traceLogFileLogger)
.Build()
.ForContext(GetType());

cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(5));
cancellationToken = cancellationTokenSource.Token;

tentacleClient = SetupHelpers.BuildTentacleClient(kubernetesAgentInstaller.SubscriptionId, agentThumbprint, serverHalibutRuntime, builder =>
{
builder.RecordMethodUsages<IAsyncClientKubernetesScriptServiceV1>(out var recordedUsages);
recordedMethodUsages = recordedUsages;
});
}

async Task SetupCluster(ClusterVersion clusterVersion)
{
clusterInstaller = new KubernetesClusterInstaller(testContext.TemporaryDirectory, kindExePath, helmExePath, kubeCtlPath, testContext.Logger);
await clusterInstaller.Install(clusterVersion);

testContext.TentacleImageAndTag = SetupHelpers.GetTentacleImageAndTag(kindExePath, clusterInstaller);
testContext.SetToolExePaths(helmExePath, kubeCtlPath);
testContext.KubeConfigPath = clusterInstaller.KubeConfigPath;
}
}
Loading

0 comments on commit 24df9db

Please sign in to comment.