Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using FluentAssertions;
using k8s;
using Newtonsoft.Json;
using NSubstitute;
using Octopus.Diagnostics;
using Octopus.Tentacle.Diagnostics;
using Octopus.Tentacle.Kubernetes.Diagnostics;
Expand All @@ -26,19 +27,20 @@ public KubernetesClientConfiguration Get()
return KubernetesClientConfiguration.BuildConfigFromConfigFile(filename);
}
}

[Test]
public async Task FetchingTimestampFromEmptyConfigMapEntryShouldBeMinValue()
{
//Arrange
var config = Substitute.For<IKubernetesConfiguration>();
var kubernetesConfigClient = new KubernetesFileWrappedProvider(KubernetesTestsGlobalContext.Instance.KubeConfigPath);
var configMapService = new Support.TestSupportConfigMapService(kubernetesConfigClient, systemLog, KubernetesAgentInstaller.Namespace);
var persistenceProvider = new PersistenceProvider("kubernetes-agent-metrics", configMapService);
var configMapService = new Support.TestSupportConfigMapService(config, kubernetesConfigClient, systemLog, KubernetesAgentInstaller.Namespace);
var persistenceProvider = new PersistenceProvider("kubernetes-agent-metrics", config, configMapService);
var metrics = new KubernetesAgentMetrics(persistenceProvider, ConfigMapNames.AgentMetricsConfigMapKey, systemLog);

//Act
var result = await metrics.GetLatestEventTimestamp(CancellationToken.None);

//Assert
result.Should().Be(DateTimeOffset.MinValue);
}
Expand All @@ -47,14 +49,15 @@ public async Task FetchingTimestampFromEmptyConfigMapEntryShouldBeMinValue()
public async Task FetchingLatestEventTimestampFromNonexistentConfigMapThrowsException()
{
//Arrange
var config = Substitute.For<IKubernetesConfiguration>();
var kubernetesConfigClient = new KubernetesFileWrappedProvider(KubernetesTestsGlobalContext.Instance.KubeConfigPath);
var configMapService = new Support.TestSupportConfigMapService(kubernetesConfigClient, systemLog, KubernetesAgentInstaller.Namespace);
var persistenceProvider = new PersistenceProvider("nonexistent-config-map", configMapService);
var configMapService = new Support.TestSupportConfigMapService(config, kubernetesConfigClient, systemLog, KubernetesAgentInstaller.Namespace);
var persistenceProvider = new PersistenceProvider("nonexistent-config-map", config, configMapService);
var metrics = new KubernetesAgentMetrics(persistenceProvider, "metrics", systemLog);

//Act
Func<Task> func = async () => await metrics.GetLatestEventTimestamp(CancellationToken.None);

//Assert
await func.Should().ThrowAsync<Exception>();
}
Expand All @@ -63,9 +66,10 @@ public async Task FetchingLatestEventTimestampFromNonexistentConfigMapThrowsExce
public async Task WritingEventToNonExistentConfigMapShouldFailSilently()
{
//Arrange
var config = Substitute.For<IKubernetesConfiguration>();
var kubernetesConfigClient = new KubernetesFileWrappedProvider(KubernetesTestsGlobalContext.Instance.KubeConfigPath);
var configMapService = new Support.TestSupportConfigMapService(kubernetesConfigClient, systemLog, KubernetesAgentInstaller.Namespace);
var persistenceProvider = new PersistenceProvider("nonexistent-config-map", configMapService);
var configMapService = new Support.TestSupportConfigMapService(config, kubernetesConfigClient, systemLog, KubernetesAgentInstaller.Namespace);
var persistenceProvider = new PersistenceProvider("nonexistent-config-map", config, configMapService);
var metrics = new KubernetesAgentMetrics(persistenceProvider, ConfigMapNames.AgentMetricsConfigMapKey, systemLog);

//Act
Expand All @@ -79,15 +83,16 @@ public async Task WritingEventToNonExistentConfigMapShouldFailSilently()
public async Task WritingEventToExistingConfigMapShouldPersistJsonEntry()
{
//Arrange
var config = Substitute.For<IKubernetesConfiguration>();
var kubernetesConfigClient = new KubernetesFileWrappedProvider(KubernetesTestsGlobalContext.Instance.KubeConfigPath);
var configMapService = new Support.TestSupportConfigMapService(kubernetesConfigClient, systemLog, KubernetesAgentInstaller.Namespace);
var persistenceProvider = new PersistenceProvider("kubernetes-agent-metrics", configMapService);
var configMapService = new Support.TestSupportConfigMapService(config, kubernetesConfigClient, systemLog, KubernetesAgentInstaller.Namespace);
var persistenceProvider = new PersistenceProvider("kubernetes-agent-metrics", config, configMapService);
var metrics = new KubernetesAgentMetrics(persistenceProvider, ConfigMapNames.AgentMetricsConfigMapKey, systemLog);

//Act
var eventTimestamp = DateTimeOffset.Now;
await metrics.TrackEvent("reason", "source", eventTimestamp, CancellationToken.None);

//Assert
var persistedDictionary = await persistenceProvider.ReadValues(CancellationToken.None);
var metricsData = persistedDictionary[ConfigMapNames.AgentMetricsConfigMapKey];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0"/>
<PackageReference Include="NSubstitute" Version="4.4.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Octopus.TestPortForwarder" Version="7.0.539" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@

namespace Octopus.Tentacle.Kubernetes.Tests.Integration.Support
{

// This is a copy of the production ConfigMapService, but allows the namespace to be explicitly
// defined.
public class TestSupportConfigMapService : KubernetesService, IKubernetesConfigMapService
{
readonly string targetNamespace;

public TestSupportConfigMapService(IKubernetesClientConfigProvider configProvider, ISystemLog log, string targetNamespace)
: base(configProvider, log)
public TestSupportConfigMapService(IKubernetesConfiguration kubernetesConfiguration, IKubernetesClientConfigProvider configProvider, ISystemLog log, string targetNamespace)
: base(configProvider, kubernetesConfiguration, log)
{
this.targetNamespace = targetNamespace;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
using Octopus.Tentacle.Contracts;
using Octopus.Tentacle.Contracts.KubernetesScriptServiceV1;
Expand All @@ -16,7 +17,10 @@ public class CapabilitiesServiceV2Fixture
[Test]
public async Task CapabilitiesAreReturned()
{
var capabilities = (await new CapabilitiesServiceV2()
var k8sDetection = Substitute.For<IKubernetesAgentDetection>();
k8sDetection.IsRunningAsKubernetesAgent.Returns(false);

var capabilities = (await new CapabilitiesServiceV2(k8sDetection)
.GetCapabilitiesAsync(CancellationToken.None))
.SupportedCapabilities;

Expand All @@ -29,18 +33,17 @@ public async Task CapabilitiesAreReturned()
[Test]
public async Task OnlyKubernetesScriptServicesAreReturnedWhenRunningAsKubernetesAgent()
{
Environment.SetEnvironmentVariable(KubernetesConfig.NamespaceVariableName, "ABC");
var k8sDetection = Substitute.For<IKubernetesAgentDetection>();
k8sDetection.IsRunningAsKubernetesAgent.Returns(true);

var capabilities = (await new CapabilitiesServiceV2()
var capabilities = (await new CapabilitiesServiceV2(k8sDetection)
.GetCapabilitiesAsync(CancellationToken.None))
.SupportedCapabilities;

capabilities.Should().BeEquivalentTo(nameof(IFileTransferService), nameof(IKubernetesScriptServiceV1));
capabilities.Count.Should().Be(2);

capabilities.Should().NotContainMatch("IScriptService*");

Environment.SetEnvironmentVariable(KubernetesConfig.NamespaceVariableName, null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,16 @@ ApplicationInstanceSelector CreateApplicationInstanceSelector(StartUpInstanceReq
IApplicationConfigurationContributor[]? additionalConfigurations = null)
{
var log = Substitute.For<ISystemLog>();
var kubernetesConfig = Substitute.For<IKubernetesConfiguration>();
var k8sDetection = Substitute.For<IKubernetesAgentDetection>();
return new ApplicationInstanceSelector(ApplicationName.Tentacle,
applicationInstanceStore,
instanceRequest ?? new StartUpDynamicInstanceRequest(),
additionalConfigurations ?? new IApplicationConfigurationContributor[0],
new Lazy<ConfigMapKeyValueStore>(() => new ConfigMapKeyValueStore(Substitute.For<IKubernetesConfigMapService>(), Substitute.For<IKubernetesMachineKeyEncryptor>())),
additionalConfigurations ?? Array.Empty<IApplicationConfigurationContributor>(),
new Lazy<ConfigMapKeyValueStore>(() => new ConfigMapKeyValueStore(kubernetesConfig,Substitute.For<IKubernetesConfigMapService>(), Substitute.For<IKubernetesMachineKeyEncryptor>())),
octopusFileSystem,
log);
log,
k8sDetection);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class KubernetesDirectoryInformationProviderFixture
{
// Sizes
const ulong Megabyte = 1000 * 1000;

[Test]
public void DuOutputParses()
{
Expand All @@ -30,10 +30,10 @@ public void DuOutputParses()
x.ArgAt<Action<string>>(3).Invoke($"{usedSize}\t/octopus");
});
var memoryCache = new MemoryCache(new MemoryCacheOptions());
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), spr, memoryCache);
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), Substitute.For<IKubernetesConfiguration>(), spr, memoryCache);
sut.GetPathUsedBytes("/octopus").Should().Be(usedSize);
}

[Test]
public void DuOutputParsesWithMultipleLines()
{
Expand All @@ -44,10 +44,10 @@ public void DuOutputParsesWithMultipleLines()
{
x.ArgAt<Action<string>>(3).Invoke($"500\t/octopus/extradir");
x.ArgAt<Action<string>>(3).Invoke($"{usedSize}\t/octopus");
x.ArgAt<Action<string>>(3).Invoke($"{usedSize+1000}\tTotal");
x.ArgAt<Action<string>>(3).Invoke($"{usedSize + 1000}\tTotal");
});
var memoryCache = new MemoryCache(new MemoryCacheOptions());
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), spr, memoryCache);
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), Substitute.For<IKubernetesConfiguration>(), spr, memoryCache);
sut.GetPathUsedBytes("/octopus").Should().Be(usedSize);
}

Expand All @@ -64,10 +64,10 @@ public void IfDuFailsWeStillGetData()
});
spr.ReturnsForAll(1);
var memoryCache = new MemoryCache(new MemoryCacheOptions());
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), spr, memoryCache);
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), Substitute.For<IKubernetesConfiguration>(), spr, memoryCache);
sut.GetPathUsedBytes("/octopus").Should().Be(usedSize);
}

[Test]
public void IfDuFailsWeLogCorrectly()
{
Expand All @@ -81,16 +81,16 @@ public void IfDuFailsWeLogCorrectly()
// stdout
x.ArgAt<Action<string>>(3).Invoke("500\t/octopus");
x.ArgAt<Action<string>>(3).Invoke($"{usedSize}\t/octopus");

// stderr
x.ArgAt<Action<string>>(4).Invoke("no permission for foo");
x.ArgAt<Action<string>>(4).Invoke("also no permission for bar");
});
spr.ReturnsForAll(1);
var memoryCache = new MemoryCache(new MemoryCacheOptions());
var sut = new KubernetesDirectoryInformationProvider(systemLog, spr, memoryCache);
var sut = new KubernetesDirectoryInformationProvider(systemLog, Substitute.For<IKubernetesConfiguration>(), spr, memoryCache);
sut.GetPathUsedBytes("/octopus").Should().Be(usedSize);

systemLog.GetLogsForCategory(LogCategory.Warning).Should().Contain("Could not reliably get disk space using du. Getting best approximation...");
systemLog.GetLogsForCategory(LogCategory.Info).Should().Contain($"Du stdout returned 500\t/octopus, {usedSize}\t/octopus");
systemLog.GetLogsForCategory(LogCategory.Info).Should().Contain("Du stderr returned no permission for foo, also no permission for bar");
Expand All @@ -102,21 +102,21 @@ public void IfDuFailsCompletelyReturnNull()
var spr = Substitute.For<ISilentProcessRunner>();
spr.ReturnsForAll(1);
var memoryCache = new MemoryCache(new MemoryCacheOptions());
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), spr, memoryCache);
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), Substitute.For<IKubernetesConfiguration>(), spr, memoryCache);
sut.GetPathUsedBytes("/octopus").Should().Be(null);
}

[Test]
public void ReturnedValueShouldBeCached()
{
var spr = Substitute.For<ISilentProcessRunner>();
spr.ReturnsForAll(1);
var baseTime = DateTimeOffset.UtcNow;
var clock = new TestClock(baseTime);
var memoryCache = new MemoryCache(new MemoryCacheOptions(){ Clock = clock});
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), spr, memoryCache);
var memoryCache = new MemoryCache(new MemoryCacheOptions() { Clock = clock });
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), Substitute.For<IKubernetesConfiguration>(), spr, memoryCache);
sut.GetPathUsedBytes("/octopus").Should().Be(null);

const ulong usedSize = 500 * Megabyte;
spr.When(x => x.ExecuteCommand("du", "-s -B 1 /octopus", "/", Arg.Any<Action<string>>(), Arg.Any<Action<string>>()))
.Do(x =>
Expand All @@ -128,18 +128,18 @@ public void ReturnedValueShouldBeCached()

sut.GetPathUsedBytes("/octopus").Should().Be(null);
}

[Test]
public void DuCacheExpiresAfter30Seconds()
{
var spr = Substitute.For<ISilentProcessRunner>();
spr.ReturnsForAll(1);
var baseTime = DateTimeOffset.UtcNow;
var clock = new TestClock(baseTime);
var memoryCache = new MemoryCache(new MemoryCacheOptions(){ Clock = clock});
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), spr, memoryCache);
var memoryCache = new MemoryCache(new MemoryCacheOptions() { Clock = clock });
var sut = new KubernetesDirectoryInformationProvider(Substitute.For<ISystemLog>(), Substitute.For<IKubernetesConfiguration>(), spr, memoryCache);
sut.GetPathUsedBytes("/octopus").Should().Be(null);

const ulong usedSize = 500 * Megabyte;
spr.When(x => x.ExecuteCommand("du", "-s -B 1 /octopus", "/", Arg.Any<Action<string>>(), Arg.Any<Action<string>>()))
.Do(x =>
Expand All @@ -149,10 +149,9 @@ public void DuCacheExpiresAfter30Seconds()
});

clock.UtcNow = baseTime + TimeSpan.FromSeconds(30);

sut.GetPathUsedBytes("/octopus").Should().Be(usedSize);
}

}

public class TestClock : ISystemClock
Expand Down
Loading