Ensure that mock collectors are available (#1270)

* Healthzz for MockZipkinCollector

* Healthzz for MockMetricCollector

* Healthzz for MockLogsCollector

* Extract HealthZ handler to common class
This commit is contained in:
Piotr Kiełkowicz 2022-09-23 20:01:19 +02:00 committed by GitHub
parent 938dfcb163
commit c5527e4533
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 109 additions and 81 deletions

View File

@ -45,7 +45,7 @@ public class AspNetTests : TestHelper
// accessible to the Windows docker container where the test application is executed by binding
// the endpoint to all network interfaces. In order to do that it is necessary to open the port
// on the firewall.
using var agent = new MockZipkinCollector(Output, host: "*");
using var agent = await MockZipkinCollector.Start(Output, host: "*");
using var fwPort = FirewallHelper.OpenWinPort(agent.Port, Output);
var testSettings = new TestSettings
{
@ -85,7 +85,7 @@ public class AspNetTests : TestHelper
// accessible to the Windows docker container where the test application is executed by binding
// the endpoint to all network interfaces. In order to do that it is necessary to open the port
// on the firewall.
using var collector = new MockMetricsCollector(Output, host: "*");
using var collector = await MockMetricsCollector.Start(Output, host: "*");
using var fwPort = FirewallHelper.OpenWinPort(collector.Port, Output);
var testSettings = new TestSettings
{

View File

@ -58,7 +58,7 @@ public class DomainNeutralTests : TestHelper
SetEnvironmentVariable("OTEL_DOTNET_AUTO_INTEGRATIONS_FILE", integrationsFile);
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
RunTestApplication(agent.Port);

View File

@ -75,7 +75,7 @@ public class GraphQLTests : TestHelper
SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName);
int aspNetCorePort = TcpPortProvider.GetOpenPort();
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
using var process = StartTestApplication(agent.Port, aspNetCorePort: aspNetCorePort);
if (process.HasExited)
{

View File

@ -36,7 +36,7 @@ public class GrpcNetClientTests : TestHelper
[Trait("Category", "EndToEnd")]
public async Task SubmitsTraces()
{
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
// Grpc.Net.Client is using various version of http communication under the hood.
// Disabling HttpClient instrumentation to have consistent set of spans.

View File

@ -20,6 +20,7 @@ using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using OpenTelemetry.Proto.Collector.Logs.V1;
using Xunit;
@ -34,9 +35,9 @@ public class MockLogsCollector : IDisposable
private readonly ITestOutputHelper _output;
private readonly TestHttpListener _listener;
private readonly BlockingCollection<global::OpenTelemetry.Proto.Logs.V1.LogRecord> _logs = new(100); // bounded to avoid memory leak
private List<Expectation> _expectations = new();
private readonly List<Expectation> _expectations = new();
public MockLogsCollector(ITestOutputHelper output, string host = "localhost")
private MockLogsCollector(ITestOutputHelper output, string host = "localhost")
{
_output = output;
_listener = new(output, HandleHttpRequests, host);
@ -52,6 +53,21 @@ public class MockLogsCollector : IDisposable
/// </summary>
public bool IsStrict { get; set; }
public static async Task<MockLogsCollector> Start(ITestOutputHelper output, string host = "localhost")
{
var collector = new MockLogsCollector(output, host);
var healthzResult = await collector._listener.VerifyHealthzAsync();
if (!healthzResult)
{
collector.Dispose();
throw new InvalidOperationException($"Cannot start {nameof(MockLogsCollector)}!");
}
return collector;
}
public void Dispose()
{
_listener.Dispose();
@ -152,12 +168,6 @@ public class MockLogsCollector : IDisposable
private void HandleHttpRequests(HttpListenerContext ctx)
{
if (ctx.Request.RawUrl.Equals("/healthz", StringComparison.OrdinalIgnoreCase))
{
CreateHealthResponse(ctx);
return;
}
if (ctx.Request.RawUrl.Equals("/v1/logs", StringComparison.OrdinalIgnoreCase))
{
var logsMessage = ExportLogsServiceRequest.Parser.ParseFrom(ctx.Request.InputStream);
@ -197,16 +207,6 @@ public class MockLogsCollector : IDisposable
ctx.Response.Close();
}
private void CreateHealthResponse(HttpListenerContext ctx)
{
ctx.Response.ContentType = "text/plain";
var buffer = Encoding.UTF8.GetBytes("OK");
ctx.Response.ContentLength64 = buffer.LongLength;
ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
ctx.Response.StatusCode = (int)HttpStatusCode.OK;
ctx.Response.Close();
}
private void WriteOutput(string msg)
{
const string name = nameof(MockLogsCollector);

View File

@ -22,6 +22,7 @@ using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;
using IntegrationTests.Helpers.Models;
using OpenTelemetry.Proto.Collector.Metrics.V1;
@ -37,7 +38,7 @@ public class MockMetricsCollector : IDisposable
private readonly ITestOutputHelper _output;
private readonly TestHttpListener _listener;
public MockMetricsCollector(ITestOutputHelper output, string host = "localhost")
private MockMetricsCollector(ITestOutputHelper output, string host = "localhost")
{
_output = output;
_listener = new(output, HandleHttpRequests, host);
@ -66,6 +67,21 @@ public class MockMetricsCollector : IDisposable
private IImmutableList<NameValueCollection> RequestHeaders { get; set; } = ImmutableList<NameValueCollection>.Empty;
public static async Task<MockMetricsCollector> Start(ITestOutputHelper output, string host = "localhost")
{
var collector = new MockMetricsCollector(output, host);
var healthzResult = await collector._listener.VerifyHealthzAsync();
if (!healthzResult)
{
collector.Dispose();
throw new InvalidOperationException($"Cannot start {nameof(MockLogsCollector)}!");
}
return collector;
}
/// <summary>
/// Wait for the given number of metric requests to appear.
/// </summary>
@ -126,12 +142,6 @@ public class MockMetricsCollector : IDisposable
{
OnRequestReceived(ctx);
if (ctx.Request.RawUrl.Equals("/healthz", StringComparison.OrdinalIgnoreCase))
{
CreateHealthResponse(ctx);
return;
}
if (ctx.Request.RawUrl.Equals("/v1/metrics", StringComparison.OrdinalIgnoreCase))
{
if (ShouldDeserializeMetrics)
@ -162,16 +172,6 @@ public class MockMetricsCollector : IDisposable
ctx.Response.Close();
}
private void CreateHealthResponse(HttpListenerContext ctx)
{
ctx.Response.ContentType = "text/plain";
var buffer = Encoding.UTF8.GetBytes("OK");
ctx.Response.ContentLength64 = buffer.LongLength;
ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
ctx.Response.StatusCode = (int)HttpStatusCode.OK;
ctx.Response.Close();
}
private void WriteOutput(string msg)
{
const string name = nameof(MockMetricsCollector);

View File

@ -38,7 +38,7 @@ public class MockZipkinCollector : IDisposable
private readonly ITestOutputHelper _output;
private readonly TestHttpListener _listener;
public MockZipkinCollector(ITestOutputHelper output, string host = "localhost")
private MockZipkinCollector(ITestOutputHelper output, string host = "localhost")
{
_output = output;
_listener = new(output, HandleHttpRequests, host, "/api/v2/spans/");
@ -67,6 +67,21 @@ public class MockZipkinCollector : IDisposable
private IImmutableList<NameValueCollection> RequestHeaders { get; set; } = ImmutableList<NameValueCollection>.Empty;
public static async Task<MockZipkinCollector> Start(ITestOutputHelper output, string host = "localhost")
{
var collector = new MockZipkinCollector(output, host);
var healthzResult = await collector._listener.VerifyHealthzAsync();
if (!healthzResult)
{
collector.Dispose();
throw new InvalidOperationException($"Cannot start {nameof(MockLogsCollector)}!");
}
return collector;
}
/// <summary>
/// Wait for the given number of spans to appear.
/// </summary>
@ -141,12 +156,6 @@ public class MockZipkinCollector : IDisposable
private void HandleHttpRequests(HttpListenerContext ctx)
{
if (ctx.Request.RawUrl.Equals("/healthz", StringComparison.OrdinalIgnoreCase))
{
CreateHealthResponse(ctx);
return;
}
if (ShouldDeserializeTraces)
{
using (var reader = new StreamReader(ctx.Request.InputStream))
@ -176,16 +185,6 @@ public class MockZipkinCollector : IDisposable
ctx.Response.Close();
}
private void CreateHealthResponse(HttpListenerContext ctx)
{
ctx.Response.ContentType = "text/plain";
var buffer = Encoding.UTF8.GetBytes("OK");
ctx.Response.ContentLength64 = buffer.LongLength;
ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
ctx.Response.StatusCode = (int)HttpStatusCode.OK;
ctx.Response.Close();
}
private void WriteOutput(string msg)
{
const string name = nameof(MockZipkinCollector);

View File

@ -16,7 +16,9 @@
using System;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
namespace IntegrationTests.Helpers;
@ -27,8 +29,9 @@ public class TestHttpListener : IDisposable
private readonly Action<HttpListenerContext> _requestHandler;
private readonly HttpListener _listener;
private readonly Thread _listenerThread;
private readonly string _prefix;
public TestHttpListener(ITestOutputHelper output, Action<HttpListenerContext> requestHandler, string host = "localhost", string sufix = "/")
public TestHttpListener(ITestOutputHelper output, Action<HttpListenerContext> requestHandler, string host, string sufix = "/")
{
_output = output;
_requestHandler = requestHandler;
@ -48,13 +51,13 @@ public class TestHttpListener : IDisposable
// See https://docs.microsoft.com/en-us/dotnet/api/system.net.httplistenerprefixcollection.add?redirectedfrom=MSDN&view=net-6.0#remarks
// for info about the host value.
Port = TcpPortProvider.GetOpenPort();
string prefix = new UriBuilder("http", host, Port, sufix).ToString();
_listener.Prefixes.Add(prefix);
_prefix = new UriBuilder("http", host, Port, sufix).ToString();
_listener.Prefixes.Add(_prefix);
// successfully listening
_listenerThread = new Thread(HandleHttpRequests);
_listenerThread.Start();
WriteOutput($"Listening on '{prefix}'");
WriteOutput($"Listening on '{_prefix}'");
return;
}
@ -76,6 +79,13 @@ public class TestHttpListener : IDisposable
/// </summary>
public int Port { get; }
public Task<bool> VerifyHealthzAsync()
{
var healhtzEndpoint = $"{_prefix.Replace("*", "localhost")}/healthz";
return HealthzHelper.TestHealtzAsync(healhtzEndpoint, nameof(MockLogsCollector), _output);
}
public void Dispose()
{
WriteOutput($"Listener is shutting down.");
@ -89,6 +99,13 @@ public class TestHttpListener : IDisposable
try
{
var ctx = _listener.GetContext();
if (ctx.Request.RawUrl.EndsWith("/healthz", StringComparison.OrdinalIgnoreCase))
{
CreateHealthResponse(ctx);
continue;
}
_requestHandler(ctx);
}
catch (HttpListenerException)
@ -112,6 +129,16 @@ public class TestHttpListener : IDisposable
}
}
private void CreateHealthResponse(HttpListenerContext ctx)
{
ctx.Response.ContentType = "text/plain";
var buffer = Encoding.UTF8.GetBytes("OK");
ctx.Response.ContentLength64 = buffer.LongLength;
ctx.Response.OutputStream.Write(buffer, 0, buffer.Length);
ctx.Response.StatusCode = (int)HttpStatusCode.OK;
ctx.Response.Close();
}
private void WriteOutput(string msg)
{
const string name = nameof(TestHttpListener);

View File

@ -46,7 +46,7 @@ public class HttpTests : TestHelper
SetEnvironmentVariable("OTEL_PROPAGATORS", propagators);
SetEnvironmentVariable("DISABLE_DistributedContextPropagator", "true");
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
const int expectedSpanCount = 3;
@ -91,11 +91,11 @@ public class HttpTests : TestHelper
[Fact]
[Trait("Category", "EndToEnd")]
public void SubmitMetrics()
public async Task SubmitMetrics()
{
const int expectedMetricRequests = 1;
using var collector = new MockMetricsCollector(Output);
using var collector = await MockMetricsCollector.Start(Output);
RunTestApplication(metricsAgentPort: collector.Port, enableClrProfiler: !IsCoreClr());
var metricRequests = collector.WaitForMetrics(expectedMetricRequests);

View File

@ -18,6 +18,7 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Extensions;
using IntegrationTests.Helpers;
@ -40,13 +41,13 @@ public class LogTests : TestHelper
[InlineData(false, true)]
[InlineData(false, false)]
[Trait("Category", "EndToEnd")]
public void SubmitLogs(bool parseStateValues, bool includeFormattedMessage)
public async Task SubmitLogs(bool parseStateValues, bool includeFormattedMessage)
{
SetEnvironmentVariable("OTEL_DOTNET_AUTO_LOGS_PARSE_STATE_VALUES", parseStateValues.ToString());
SetEnvironmentVariable("OTEL_DOTNET_AUTO_LOGS_INCLUDE_FORMATTED_MESSAGE", includeFormattedMessage.ToString());
int aspNetCorePort = TcpPortProvider.GetOpenPort();
using var collector = new MockLogsCollector(Output);
using var collector = await MockLogsCollector.Start(Output);
if (parseStateValues || includeFormattedMessage)
{
collector.Expect(logRecord => Convert.ToString(logRecord.Body) == "{ \"stringValue\": \"Information from Test App.\" }");

View File

@ -43,7 +43,7 @@ public class MongoDBTests : TestHelper
[Trait("Containers", "Linux")]
public async Task SubmitsTraces()
{
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
RunTestApplication(agent.Port, arguments: $"--mongo-db {_mongoDB.Port}");
var spans = await agent.WaitForSpansAsync(3);
Assert.True(spans.Count >= 3, $"Expecting at least 3 spans, only received {spans.Count}");

View File

@ -44,7 +44,7 @@ public class MySqlDataTests : TestHelper
[Trait("Containers", "Linux")]
public async Task SubmitsTraces()
{
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
RunTestApplication(agent.Port, arguments: $"--mysql {_mySql.Port}", enableClrProfiler: !IsCoreClr());

View File

@ -42,7 +42,7 @@ public class NpqsqlTests : TestHelper
[Trait("Containers", "Linux")]
public async Task SubmitsTraces()
{
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
RunTestApplication(agent.Port, arguments: $"--postgres {_postgres.Port}", enableClrProfiler: !IsCoreClr());
var spans = await agent.WaitForSpansAsync(1);

View File

@ -37,7 +37,7 @@ public class PluginsTests : TestHelper
{
SetEnvironmentVariable("OTEL_DOTNET_AUTO_PLUGINS", "TestApplication.Plugins.Plugin, TestApplication.Plugins, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
using var collector = new MockZipkinCollector(Output);
using var collector = await MockZipkinCollector.Start(Output);
RunTestApplication(collector.Port);
var spans = await collector.WaitForSpansAsync(1);
@ -46,11 +46,11 @@ public class PluginsTests : TestHelper
[Fact]
[Trait("Category", "EndToEnd")]
public void SubmitMetrics()
public async Task SubmitMetrics()
{
SetEnvironmentVariable("OTEL_DOTNET_AUTO_PLUGINS", "TestApplication.Plugins.Plugin, TestApplication.Plugins, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
using var collector = new MockMetricsCollector(Output);
using var collector = await MockMetricsCollector.Start(Output);
RunTestApplication(metricsAgentPort: collector.Port);
var metricRequests = collector.WaitForMetrics(1);

View File

@ -16,6 +16,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Extensions;
using IntegrationTests.Helpers;
@ -35,9 +36,9 @@ public class RuntimeTests : TestHelper
[Fact]
[Trait("Category", "EndToEnd")]
public void SubmitMetrics()
public async Task SubmitMetrics()
{
using var collector = new MockMetricsCollector(Output);
using var collector = await MockMetricsCollector.Start(Output);
using var process = StartTestApplication(metricsAgentPort: collector.Port, enableClrProfiler: !IsCoreClr());
try

View File

@ -119,12 +119,12 @@ public class SmokeTests : TestHelper
[Fact]
[Trait("Category", "EndToEnd")]
public void SubmitMetrics()
public async Task SubmitMetrics()
{
SetEnvironmentVariable("OTEL_DOTNET_AUTO_METRICS_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary");
const int expectedMetricRequests = 1;
using var collector = new MockMetricsCollector(Output);
using var collector = await MockMetricsCollector.Start(Output);
RunTestApplication(metricsAgentPort: collector.Port);
var metricRequests = collector.WaitForMetrics(expectedMetricRequests);
@ -225,7 +225,7 @@ public class SmokeTests : TestHelper
{
SetEnvironmentVariable("OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES", "MyCompany.MyProduct.MyLibrary");
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
RunTestApplication(agent.Port, enableStartupHook: enableStartupHook);
return await agent.WaitForSpansAsync(2);

View File

@ -43,7 +43,7 @@ public class SqlClientTests : TestHelper
[Trait("Containers", "Linux")]
public async Task SubmitTraces()
{
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
const int expectedSpanCount = 8;

View File

@ -44,7 +44,7 @@ public class StackExchangeRedisTests : TestHelper
[Trait("Containers", "Linux")]
public async Task SubmitsTraces()
{
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
RunTestApplication(agent.Port, arguments: $"--redis {_redis.Port}");

View File

@ -42,7 +42,7 @@ public class StrongNamedTests : TestHelper
SetEnvironmentVariable("OTEL_DOTNET_AUTO_INTEGRATIONS_FILE", integrationsFile);
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
RunTestApplication(agent.Port);

View File

@ -38,7 +38,7 @@ public abstract class WcfTestsBase : TestHelper, IDisposable
[Trait("Category", "EndToEnd")]
public async Task SubmitsTraces()
{
using var agent = new MockZipkinCollector(Output);
using var agent = await MockZipkinCollector.Start(Output);
var serverHelper = new WcfServerTestHelper(Output);
_serverProcess = serverHelper.RunWcfServer(agent.Port);