mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
broke out butterfly into seperate project (#521)
* broke out butterfly into seperate project * nearly did it... * updated docs as I have broken the butterfly code off into a seperate dll
This commit is contained in:
parent
24f8a18579
commit
37fb32b7f5
@ -1,19 +1,29 @@
|
|||||||
Tracing
|
Tracing
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Ocelot providers tracing functionality from the excellent `Butterfly <https://github.com/liuhaoyang/butterfly>`_ project.
|
This page details how to perform distributed tracing with Ocelot. At the moment we only support Butterfly but other tracers might just work without
|
||||||
|
anything Ocelot specific.
|
||||||
|
|
||||||
|
Butterfly
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
Ocelot providers tracing functionality from the excellent `Butterfly <https://github.com/liuhaoyang/butterfly>`_ project. The code for the Ocelot integration
|
||||||
|
can be found `here <https://github.com/ThreeMammals/Ocelot.Tracing.Butterfly>`_.
|
||||||
|
|
||||||
In order to use the tracing please read the Butterfly documentation.
|
In order to use the tracing please read the Butterfly documentation.
|
||||||
|
|
||||||
In ocelot you need to do the following if you wish to trace a ReRoute.
|
In ocelot you need to do the following if you wish to trace a ReRoute.
|
||||||
|
|
||||||
|
``Install-Package Ocelot.Tracing.Butterfly``
|
||||||
|
|
||||||
In your ConfigureServices method
|
In your ConfigureServices method
|
||||||
|
|
||||||
.. code-block:: csharp
|
.. code-block:: csharp
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddOcelot()
|
.AddOcelot()
|
||||||
.AddOpenTracing(option =>
|
// this comes from Ocelot.Tracing.Butterfly package
|
||||||
|
.AddButterfly(option =>
|
||||||
{
|
{
|
||||||
//this is the url that the butterfly collector server is running on...
|
//this is the url that the butterfly collector server is running on...
|
||||||
option.CollectorUrl = "http://localhost:9618";
|
option.CollectorUrl = "http://localhost:9618";
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
namespace Ocelot.Configuration.Creator
|
namespace Ocelot.Configuration.Creator
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using Butterfly.Client.Tracing;
|
using Logging;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Ocelot.Requester;
|
|
||||||
|
|
||||||
public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator
|
public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator
|
||||||
{
|
{
|
||||||
private readonly IServiceTracer _tracer;
|
private readonly ITracer _tracer;
|
||||||
|
|
||||||
public HttpHandlerOptionsCreator(IServiceProvider services)
|
public HttpHandlerOptionsCreator(IServiceProvider services)
|
||||||
{
|
{
|
||||||
_tracer = services.GetService<IServiceTracer>();
|
_tracer = services.GetService<ITracer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpHandlerOptions Create(FileHttpHandlerOptions options)
|
public HttpHandlerOptions Create(FileHttpHandlerOptions options)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
using Butterfly.Client.AspNetCore;
|
|
||||||
using CacheManager.Core;
|
using CacheManager.Core;
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@ -17,8 +16,6 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings);
|
IOcelotBuilder AddCacheManager(Action<ConfigurationBuilderCachePart> settings);
|
||||||
|
|
||||||
IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings);
|
|
||||||
|
|
||||||
IOcelotAdministrationBuilder AddAdministration(string path, string secret);
|
IOcelotAdministrationBuilder AddAdministration(string path, string secret);
|
||||||
|
|
||||||
IOcelotAdministrationBuilder AddAdministration(string path, Action<IdentityServerAuthenticationOptions> configOptions);
|
IOcelotAdministrationBuilder AddAdministration(string path, Action<IdentityServerAuthenticationOptions> configOptions);
|
||||||
|
@ -40,10 +40,8 @@ namespace Ocelot.DependencyInjection
|
|||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Butterfly.Client.AspNetCore;
|
|
||||||
using Ocelot.Infrastructure;
|
using Ocelot.Infrastructure;
|
||||||
using Ocelot.Infrastructure.Consul;
|
using Ocelot.Infrastructure.Consul;
|
||||||
using Butterfly.Client.Tracing;
|
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
using ServiceDiscovery.Providers;
|
using ServiceDiscovery.Providers;
|
||||||
using Steeltoe.Common.Discovery;
|
using Steeltoe.Common.Discovery;
|
||||||
@ -228,12 +226,6 @@ namespace Ocelot.DependencyInjection
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings)
|
|
||||||
{
|
|
||||||
_services.AddButterfly(settings);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
||||||
{
|
{
|
||||||
_services.AddHostedService<FileConfigurationPoller>();
|
_services.AddHostedService<FileConfigurationPoller>();
|
||||||
|
@ -2,26 +2,20 @@
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.DiagnosticAdapter;
|
using Microsoft.Extensions.DiagnosticAdapter;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Butterfly.Client.AspNetCore;
|
|
||||||
using Butterfly.OpenTracing;
|
|
||||||
using Ocelot.Middleware;
|
using Ocelot.Middleware;
|
||||||
using Butterfly.Client.Tracing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Ocelot.Infrastructure.Extensions;
|
|
||||||
using Ocelot.Requester;
|
|
||||||
|
|
||||||
namespace Ocelot.Logging
|
namespace Ocelot.Logging
|
||||||
{
|
{
|
||||||
public class OcelotDiagnosticListener
|
public class OcelotDiagnosticListener
|
||||||
{
|
{
|
||||||
private readonly IServiceTracer _tracer;
|
|
||||||
private readonly IOcelotLogger _logger;
|
private readonly IOcelotLogger _logger;
|
||||||
|
private readonly ITracer _tracer;
|
||||||
|
|
||||||
public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceProvider services)
|
public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_tracer = services.GetService<IServiceTracer>();
|
|
||||||
_logger = factory.CreateLogger<OcelotDiagnosticListener>();
|
_logger = factory.CreateLogger<OcelotDiagnosticListener>();
|
||||||
|
_tracer = serviceProvider.GetService<ITracer>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[DiagnosticName("Ocelot.MiddlewareException")]
|
[DiagnosticName("Ocelot.MiddlewareException")]
|
||||||
@ -67,29 +61,7 @@ namespace Ocelot.Logging
|
|||||||
|
|
||||||
private void Event(HttpContext httpContext, string @event)
|
private void Event(HttpContext httpContext, string @event)
|
||||||
{
|
{
|
||||||
// todo - if the user isnt using tracing the code gets here and will blow up on
|
_tracer?.Event(httpContext, @event);
|
||||||
// _tracer.Tracer.TryExtract..
|
|
||||||
if(_tracer == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var span = httpContext.GetSpan();
|
|
||||||
|
|
||||||
if(span == null)
|
|
||||||
{
|
|
||||||
var spanBuilder = new SpanBuilder($"server {httpContext.Request.Method} {httpContext.Request.Path}");
|
|
||||||
if (_tracer.Tracer.TryExtract(out var spanContext, httpContext.Request.Headers, (c, k) => c[k].GetValue(),
|
|
||||||
c => c.Select(x => new KeyValuePair<string, string>(x.Key, x.Value.GetValue())).GetEnumerator()))
|
|
||||||
{
|
|
||||||
spanBuilder.AsChildOf(spanContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
span = _tracer.Start(spanBuilder);
|
|
||||||
httpContext.SetSpan(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
span?.Log(LogField.CreateNew().Event(@event));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
|
||||||
@ -25,10 +25,6 @@
|
|||||||
<DebugSymbols>True</DebugSymbols>
|
<DebugSymbols>True</DebugSymbols>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Butterfly.Client" Version="0.0.8" />
|
|
||||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8">
|
|
||||||
<NoWarn>NU1701</NoWarn>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="FluentValidation" Version="7.6.104" />
|
<PackageReference Include="FluentValidation" Version="7.6.104" />
|
||||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
|
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.6.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.1" />
|
||||||
|
@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Butterfly.Client.Tracing;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Logging;
|
using Ocelot.Logging;
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
using System;
|
namespace Ocelot.Requester
|
||||||
|
{
|
||||||
|
using Logging;
|
||||||
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Butterfly.Client.Tracing;
|
|
||||||
using Butterfly.OpenTracing;
|
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
|
|
||||||
namespace Ocelot.Requester
|
|
||||||
{
|
|
||||||
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
|
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
|
||||||
{
|
{
|
||||||
private readonly IServiceTracer _tracer;
|
private readonly ITracer _tracer;
|
||||||
private readonly IRequestScopedDataRepository _repo;
|
private readonly IRequestScopedDataRepository _repo;
|
||||||
private const string PrefixSpanId = "ot-spanId";
|
|
||||||
|
|
||||||
public OcelotHttpTracingHandler(
|
public OcelotHttpTracingHandler(
|
||||||
IServiceTracer tracer,
|
ITracer tracer,
|
||||||
IRequestScopedDataRepository repo,
|
IRequestScopedDataRepository repo,
|
||||||
HttpMessageHandler httpMessageHandler = null)
|
HttpMessageHandler httpMessageHandler = null)
|
||||||
{
|
{
|
||||||
@ -28,46 +26,8 @@ namespace Ocelot.Requester
|
|||||||
HttpRequestMessage request,
|
HttpRequestMessage request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual async Task<HttpResponseMessage> TracingSendAsync(
|
return _tracer.SendAsync(request, cancellationToken, x => _repo.Add("TraceId", x), (r,c) => base.SendAsync(r, c));
|
||||||
ISpan span,
|
|
||||||
HttpRequestMessage request,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (request.Headers.Contains(PrefixSpanId))
|
|
||||||
{
|
|
||||||
request.Headers.Remove(PrefixSpanId);
|
|
||||||
request.Headers.TryAddWithoutValidation(PrefixSpanId, span.SpanContext.SpanId);
|
|
||||||
}
|
|
||||||
|
|
||||||
_repo.Add("TraceId", span.SpanContext.TraceId);
|
|
||||||
|
|
||||||
span.Tags.Client().Component("HttpClient")
|
|
||||||
.HttpMethod(request.Method.Method)
|
|
||||||
.HttpUrl(request.RequestUri.OriginalString)
|
|
||||||
.HttpHost(request.RequestUri.Host)
|
|
||||||
.HttpPath(request.RequestUri.PathAndQuery)
|
|
||||||
.PeerAddress(request.RequestUri.OriginalString)
|
|
||||||
.PeerHostName(request.RequestUri.Host)
|
|
||||||
.PeerPort(request.RequestUri.Port);
|
|
||||||
|
|
||||||
_tracer.Tracer.Inject(span.SpanContext, request.Headers, (c, k, v) =>
|
|
||||||
{
|
|
||||||
if (!c.Contains(k))
|
|
||||||
{
|
|
||||||
c.Add(k, v);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
span.Log(LogField.CreateNew().ClientSend());
|
|
||||||
|
|
||||||
var responseMessage = await base.SendAsync(request, cancellationToken);
|
|
||||||
|
|
||||||
span.Log(LogField.CreateNew().ClientReceive());
|
|
||||||
|
|
||||||
return responseMessage;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
namespace Ocelot.Requester
|
namespace Ocelot.Requester
|
||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using Butterfly.Client.Tracing;
|
using Logging;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
public class TracingHandlerFactory : ITracingHandlerFactory
|
public class TracingHandlerFactory : ITracingHandlerFactory
|
||||||
{
|
{
|
||||||
private readonly IServiceTracer _tracer;
|
private readonly ITracer _tracer;
|
||||||
private readonly IRequestScopedDataRepository _repo;
|
private readonly IRequestScopedDataRepository _repo;
|
||||||
|
|
||||||
public TracingHandlerFactory(
|
public TracingHandlerFactory(
|
||||||
@ -15,7 +15,7 @@ namespace Ocelot.Requester
|
|||||||
IRequestScopedDataRepository repo)
|
IRequestScopedDataRepository repo)
|
||||||
{
|
{
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_tracer = services.GetService<IServiceTracer>();
|
_tracer = services.GetService<ITracer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ITracingHandler Get()
|
public ITracingHandler Get()
|
||||||
|
@ -1,284 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Ocelot.Configuration.File;
|
|
||||||
using Shouldly;
|
|
||||||
using TestStack.BDDfy;
|
|
||||||
using Xunit;
|
|
||||||
using Butterfly.Client.AspNetCore;
|
|
||||||
using static Rafty.Infrastructure.Wait;
|
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
|
||||||
{
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
public class ButterflyTracingTests : IDisposable
|
|
||||||
{
|
|
||||||
private IWebHost _serviceOneBuilder;
|
|
||||||
private IWebHost _serviceTwoBuilder;
|
|
||||||
private IWebHost _fakeButterfly;
|
|
||||||
private readonly Steps _steps;
|
|
||||||
private string _downstreamPathOne;
|
|
||||||
private string _downstreamPathTwo;
|
|
||||||
private int _butterflyCalled;
|
|
||||||
private readonly ITestOutputHelper _output;
|
|
||||||
|
|
||||||
public ButterflyTracingTests(ITestOutputHelper output)
|
|
||||||
{
|
|
||||||
_output = output;
|
|
||||||
_steps = new Steps();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_forward_tracing_information_from_ocelot_and_downstream_services()
|
|
||||||
{
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/api/values",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 51887,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpstreamPathTemplate = "/api001/values",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions
|
|
||||||
{
|
|
||||||
UseTracing = true
|
|
||||||
},
|
|
||||||
QoSOptions = new FileQoSOptions
|
|
||||||
{
|
|
||||||
ExceptionsAllowedBeforeBreaking = 3,
|
|
||||||
DurationOfBreak = 10,
|
|
||||||
TimeoutValue = 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/api/values",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 51388,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpstreamPathTemplate = "/api002/values",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions
|
|
||||||
{
|
|
||||||
UseTracing = true
|
|
||||||
},
|
|
||||||
QoSOptions = new FileQoSOptions
|
|
||||||
{
|
|
||||||
ExceptionsAllowedBeforeBreaking = 3,
|
|
||||||
DurationOfBreak = 10,
|
|
||||||
TimeoutValue = 5000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var butterflyUrl = "http://localhost:9618";
|
|
||||||
|
|
||||||
this.Given(x => GivenFakeButterfly(butterflyUrl))
|
|
||||||
.And(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl))
|
|
||||||
.And(x => GivenServiceTwoIsRunning("http://localhost:51388", "/api/values", 200, "Hello from Tom", butterflyUrl))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api002/values"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Tom"))
|
|
||||||
.BDDfy();
|
|
||||||
|
|
||||||
var commandOnAllStateMachines = WaitFor(10000).Until(() => _butterflyCalled >= 4);
|
|
||||||
|
|
||||||
_output.WriteLine($"_butterflyCalled is {_butterflyCalled}");
|
|
||||||
|
|
||||||
commandOnAllStateMachines.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_return_tracing_header()
|
|
||||||
{
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/api/values",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 51387,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpstreamPathTemplate = "/api001/values",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions
|
|
||||||
{
|
|
||||||
UseTracing = true
|
|
||||||
},
|
|
||||||
QoSOptions = new FileQoSOptions
|
|
||||||
{
|
|
||||||
ExceptionsAllowedBeforeBreaking = 3,
|
|
||||||
DurationOfBreak = 10,
|
|
||||||
TimeoutValue = 5000
|
|
||||||
},
|
|
||||||
DownstreamHeaderTransform = new Dictionary<string, string>()
|
|
||||||
{
|
|
||||||
{"Trace-Id", "{TraceId}"},
|
|
||||||
{"Tom", "Laura"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var butterflyUrl = "http://localhost:9618";
|
|
||||||
|
|
||||||
this.Given(x => GivenFakeButterfly(butterflyUrl))
|
|
||||||
.And(x => GivenServiceOneIsRunning("http://localhost:51387", "/api/values", 200, "Hello from Laura", butterflyUrl))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingButterfly(butterflyUrl))
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api001/values"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.And(x => _steps.ThenTheTraceHeaderIsSet("Trace-Id"))
|
|
||||||
.And(x => _steps.ThenTheResponseHeaderIs("Tom", "Laura"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl)
|
|
||||||
{
|
|
||||||
_serviceOneBuilder = new WebHostBuilder()
|
|
||||||
.UseUrls(baseUrl)
|
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseIISIntegration()
|
|
||||||
.ConfigureServices(services => {
|
|
||||||
services.AddButterfly(option =>
|
|
||||||
{
|
|
||||||
option.CollectorUrl = butterflyUrl;
|
|
||||||
option.Service = "Service One";
|
|
||||||
option.IgnoredRoutesRegexPatterns = new string[0];
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.UsePathBase(basePath);
|
|
||||||
app.Run(async context =>
|
|
||||||
{
|
|
||||||
_downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
|
|
||||||
|
|
||||||
if(_downstreamPathOne != basePath)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = statusCode;
|
|
||||||
await context.Response.WriteAsync("downstream path didnt match base path");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = statusCode;
|
|
||||||
await context.Response.WriteAsync(responseBody);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_serviceOneBuilder.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenFakeButterfly(string baseUrl)
|
|
||||||
{
|
|
||||||
_fakeButterfly = new WebHostBuilder()
|
|
||||||
.UseUrls(baseUrl)
|
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseIISIntegration()
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.Run(async context =>
|
|
||||||
{
|
|
||||||
_butterflyCalled++;
|
|
||||||
await context.Response.WriteAsync("OK...");
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_fakeButterfly.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody, string butterflyUrl)
|
|
||||||
{
|
|
||||||
_serviceTwoBuilder = new WebHostBuilder()
|
|
||||||
.UseUrls(baseUrl)
|
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseIISIntegration()
|
|
||||||
.ConfigureServices(services => {
|
|
||||||
services.AddButterfly(option =>
|
|
||||||
{
|
|
||||||
option.CollectorUrl = butterflyUrl;
|
|
||||||
option.Service = "Service Two";
|
|
||||||
option.IgnoredRoutesRegexPatterns = new string[0];
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.UsePathBase(basePath);
|
|
||||||
app.Run(async context =>
|
|
||||||
{
|
|
||||||
_downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
|
|
||||||
|
|
||||||
if(_downstreamPathTwo != basePath)
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = statusCode;
|
|
||||||
await context.Response.WriteAsync("downstream path didnt match base path");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = statusCode;
|
|
||||||
await context.Response.WriteAsync(responseBody);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_serviceTwoBuilder.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath)
|
|
||||||
{
|
|
||||||
_downstreamPathOne.ShouldBe(expectedDownstreamPathOne);
|
|
||||||
_downstreamPathTwo.ShouldBe(expectedDownstreamPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_serviceOneBuilder?.Dispose();
|
|
||||||
_serviceTwoBuilder?.Dispose();
|
|
||||||
_fakeButterfly?.Dispose();
|
|
||||||
_steps.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,6 +31,7 @@ using static Ocelot.Infrastructure.Wait;
|
|||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
namespace Ocelot.AcceptanceTests
|
||||||
{
|
{
|
||||||
|
using Butterfly;
|
||||||
using Configuration.Repository;
|
using Configuration.Repository;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
|
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
|
||||||
@ -146,44 +147,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_ocelotClient = _ocelotServer.CreateClient();
|
_ocelotClient = _ocelotServer.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void GivenOcelotIsRunningUsingButterfly(string butterflyUrl)
|
|
||||||
{
|
|
||||||
_webHostBuilder = new WebHostBuilder();
|
|
||||||
|
|
||||||
_webHostBuilder
|
|
||||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
|
||||||
{
|
|
||||||
config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath);
|
|
||||||
var env = hostingContext.HostingEnvironment;
|
|
||||||
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
|
|
||||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false);
|
|
||||||
config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false);
|
|
||||||
config.AddEnvironmentVariables();
|
|
||||||
})
|
|
||||||
.ConfigureServices(s =>
|
|
||||||
{
|
|
||||||
s.AddOcelot()
|
|
||||||
.AddOpenTracing(option =>
|
|
||||||
{
|
|
||||||
//this is the url that the butterfly collector server is running on...
|
|
||||||
option.CollectorUrl = butterflyUrl;
|
|
||||||
option.Service = "Ocelot";
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.Use(async (context, next) =>
|
|
||||||
{
|
|
||||||
await next.Invoke();
|
|
||||||
});
|
|
||||||
app.UseOcelot().Wait();
|
|
||||||
});
|
|
||||||
|
|
||||||
_ocelotServer = new TestServer(_webHostBuilder);
|
|
||||||
|
|
||||||
_ocelotClient = _ocelotServer.CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void GivenIWait(int wait)
|
internal void GivenIWait(int wait)
|
||||||
{
|
{
|
||||||
Thread.Sleep(wait);
|
Thread.Sleep(wait);
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using Butterfly.Client.Tracing;
|
|
||||||
using Butterfly.OpenTracing;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Ocelot.Configuration;
|
using Ocelot.Configuration;
|
||||||
using Ocelot.Configuration.Creator;
|
using Ocelot.Configuration.Creator;
|
||||||
@ -12,6 +10,12 @@ using Xunit;
|
|||||||
|
|
||||||
namespace Ocelot.UnitTests.Configuration
|
namespace Ocelot.UnitTests.Configuration
|
||||||
{
|
{
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
|
||||||
public class HttpHandlerOptionsCreatorTests
|
public class HttpHandlerOptionsCreatorTests
|
||||||
{
|
{
|
||||||
private IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
|
private IHttpHandlerOptionsCreator _httpHandlerOptionsCreator;
|
||||||
@ -155,23 +159,21 @@ namespace Ocelot.UnitTests.Configuration
|
|||||||
|
|
||||||
private void GivenARealTracer()
|
private void GivenARealTracer()
|
||||||
{
|
{
|
||||||
var tracer = new RealTracer();
|
var tracer = new FakeTracer();
|
||||||
_serviceCollection.AddSingleton<IServiceTracer, RealTracer>();
|
_serviceCollection.AddSingleton<ITracer, FakeTracer>();
|
||||||
_serviceProvider = _serviceCollection.BuildServiceProvider();
|
_serviceProvider = _serviceCollection.BuildServiceProvider();
|
||||||
_httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceProvider);
|
_httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RealTracer : IServiceTracer
|
class FakeTracer : ITracer
|
||||||
{
|
{
|
||||||
public ITracer Tracer => throw new NotImplementedException();
|
public void Event(HttpContext httpContext, string @event)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public string ServiceName => throw new NotImplementedException();
|
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken, Action<string> addTraceIdToRepo,
|
||||||
|
Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> baseSendAsync)
|
||||||
public string Environment => throw new NotImplementedException();
|
|
||||||
|
|
||||||
public string Identity => throw new NotImplementedException();
|
|
||||||
|
|
||||||
public ISpan Start(ISpanBuilder spanBuilder)
|
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ using Ocelot.Middleware.Multiplexer;
|
|||||||
|
|
||||||
namespace Ocelot.UnitTests.DependencyInjection
|
namespace Ocelot.UnitTests.DependencyInjection
|
||||||
{
|
{
|
||||||
|
using Butterfly;
|
||||||
|
|
||||||
public class OcelotBuilderTests
|
public class OcelotBuilderTests
|
||||||
{
|
{
|
||||||
private readonly IServiceCollection _services;
|
private readonly IServiceCollection _services;
|
||||||
@ -140,15 +142,6 @@ namespace Ocelot.UnitTests.DependencyInjection
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_set_up_tracing()
|
|
||||||
{
|
|
||||||
this.Given(x => WhenISetUpOcelotServices())
|
|
||||||
.When(x => WhenISetUpOpentracing())
|
|
||||||
.When(x => WhenIAccessOcelotHttpTracingHandler())
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_set_up_without_passing_in_config()
|
public void should_set_up_without_passing_in_config()
|
||||||
{
|
{
|
||||||
@ -387,24 +380,6 @@ namespace Ocelot.UnitTests.DependencyInjection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WhenISetUpOpentracing()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ocelotBuilder.AddOpenTracing(
|
|
||||||
option =>
|
|
||||||
{
|
|
||||||
option.CollectorUrl = "http://localhost:9618";
|
|
||||||
option.Service = "Ocelot.ManualTest";
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_ex = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WhenIAccessLoggerFactory()
|
private void WhenIAccessLoggerFactory()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
|
namespace Ocelot.UnitTests.Requester
|
||||||
|
{
|
||||||
using System;
|
using System;
|
||||||
using Butterfly.Client.Tracing;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ocelot.Infrastructure.RequestData;
|
using Ocelot.Infrastructure.RequestData;
|
||||||
using Ocelot.Requester;
|
using Ocelot.Requester;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.Requester
|
|
||||||
{
|
|
||||||
public class TracingHandlerFactoryTests
|
public class TracingHandlerFactoryTests
|
||||||
{
|
{
|
||||||
private TracingHandlerFactory _factory;
|
private readonly TracingHandlerFactory _factory;
|
||||||
private Mock<IServiceTracer> _tracer;
|
private Mock<ITracer> _tracer;
|
||||||
private IServiceCollection _serviceCollection;
|
private IServiceCollection _serviceCollection;
|
||||||
private IServiceProvider _serviceProvider;
|
private IServiceProvider _serviceProvider;
|
||||||
private Mock<IRequestScopedDataRepository> _repo;
|
private Mock<IRequestScopedDataRepository> _repo;
|
||||||
|
|
||||||
public TracingHandlerFactoryTests()
|
public TracingHandlerFactoryTests()
|
||||||
{
|
{
|
||||||
_tracer = new Mock<IServiceTracer>();
|
_tracer = new Mock<ITracer>();
|
||||||
_serviceCollection = new ServiceCollection();
|
_serviceCollection = new ServiceCollection();
|
||||||
_serviceCollection.AddSingleton<IServiceTracer>(_tracer.Object);
|
_serviceCollection.AddSingleton<ITracer>(_tracer.Object);
|
||||||
_serviceProvider = _serviceCollection.BuildServiceProvider();
|
_serviceProvider = _serviceCollection.BuildServiceProvider();
|
||||||
_repo = new Mock<IRequestScopedDataRepository>();
|
_repo = new Mock<IRequestScopedDataRepository>();
|
||||||
_factory = new TracingHandlerFactory(_serviceProvider, _repo.Object);
|
_factory = new TracingHandlerFactory(_serviceProvider, _repo.Object);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user