From 9f1fb002c15a8335c11097b1a808a39f98db8261 Mon Sep 17 00:00:00 2001 From: Tom Pallister Date: Thu, 1 Mar 2018 12:58:36 +0000 Subject: [PATCH] Feature/#246 (#252) * failing test * failing test but needs real butterfly server running..need to fix that...also worked out ive broken tracing...yey * brought in butterfly source code so i can work out how to write acceptance tests for this... * fixed the bug but still need to fix tracing * tracing working again across services but need to make tracing hook into new Ocelot middleware as it still uses asp.net middleware * removed butterfly libs brought in for testing --- docs/favicon.ico | Bin 1150 -> 8313 bytes docs/features/administration.rst | 4 +- docs/features/authentication.rst | 4 +- docs/features/configuration.rst | 2 +- docs/features/raft.rst | 2 +- docs/features/tracing.rst | 2 +- .../ConsulFileConfigurationPoller.cs | 10 +- .../DependencyInjection/OcelotBuilder.cs | 12 +- .../Middleware/OcelotMiddlewareExtensions.cs | 11 + src/Ocelot/Ocelot.csproj | 2 +- ...DelegatingHandlerHandlerProviderFactory.cs | 11 +- src/Ocelot/Requester/HttpClientBuilder.cs | 4 +- .../Requester/ITracingHandlerFactory.cs | 7 + .../Requester/OcelotHttpTracingHandler.cs | 5 - src/Ocelot/Requester/TracingHandlerFactory.cs | 32 +++ .../ButterflyTracingTests.cs | 225 ++++++++++++++++++ .../ConfigurationInConsulTests.cs | 9 +- .../Ocelot.AcceptanceTests.csproj | 101 ++++---- test/Ocelot.AcceptanceTests/Steps.cs | 45 ++++ test/Ocelot.ManualTest/Program.cs | 25 +- test/Ocelot.ManualTest/configuration.json | 4 +- test/Ocelot.UnitTests/Ocelot.UnitTests.csproj | 1 + ...atingHandlerHandlerProviderFactoryTests.cs | 4 +- .../Requester/TracingHandlerFactoryTests.cs | 27 +++ 24 files changed, 449 insertions(+), 100 deletions(-) create mode 100644 src/Ocelot/Requester/ITracingHandlerFactory.cs create mode 100644 src/Ocelot/Requester/TracingHandlerFactory.cs create mode 100644 test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs create mode 100644 test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs diff --git a/docs/favicon.ico b/docs/favicon.ico index ee470e42facff8210727fb7caca55407becc0135..5e606f749c664af11832e2ee0309726dfb664e51 100644 GIT binary patch literal 8313 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 G`M&^cYb`1O literal 1150 zcmZuwTS!z<6g@hck8yM!&Zsl@&OLKS$1=k5M+Y>f(jbcrA}oV`>cgK33H&H}Kv5(Z zN?N|n2$JeUixi;;r6mj%rB)CVRD?uSls_Xy?$%Z>X?ccq_ugx-bV(Vh-K0jAfHK4LS;MUkI;{y8Cq>Dr=dFzQ>w!tkQ3dZ1P|rQYw^H$vw5E8tR&G+4dLc?m z^I|{uc2@3gaHePSd%pq-m_sD#un%$0!_`UMT1%AFOSEWx?yV1%ZqR)>{B7O<7UmFt zOSk(Q=R9)ysN2&tP452x{Wfd5$DO4beuee$*Yf{2$A}TG_dlkFxE3O%?N>F|etU99 zXkF<*Z|}7&M-Cswnx-*$`2EYd&jWd*%jKpLnKrz=b?kE74WhEfXJ>=D^RC10B1ovI zsd?|g12r-sK6xDa0ibQxJpWK^md&dy;!I)vSYJ{f(YB^=Pq8Oq+?k$rD-g&##wB6I?{Pz) m)CkcK#5)uTp%)7gn1?Qf;LB5l6}{+nK{L8Wh<7K2VDBGM+S%6t diff --git a/docs/features/administration.rst b/docs/features/administration.rst index 425c958e..dd08feab 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -21,7 +21,7 @@ All you need to do to hook into your own IdentityServer is add the following to }; services - .AddOcelot(Configuration) + .AddOcelot() .AddAdministration("/administration", options); } @@ -51,7 +51,7 @@ The secret is the client secret that Ocelot's internal IdentityServer will use t public virtual void ConfigureServices(IServiceCollection services) { services - .AddOcelot(Configuration) + .AddOcelot() .AddAdministration("/administration", "secret"); } diff --git a/docs/features/authentication.rst b/docs/features/authentication.rst index dd78fcfa..78aa890f 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -63,7 +63,7 @@ If you want to authenticate using JWT tokens maybe from a provider like Auth0 yo x.Audience = "test"; }); - services.AddOcelot(Configuration); + services.AddOcelot(); } Then map the authentication provider key to a ReRoute in your configuration e.g. @@ -111,7 +111,7 @@ In order to use IdentityServer bearer tokens register your IdentityServer servic services.AddAuthentication() .AddIdentityServerAuthentication(authenticationProviderKey, options); - services.AddOcelot(Configuration); + services.AddOcelot(); } Then map the authentication provider key to a ReRoute in your configuration e.g. diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 5aa054f8..93cae138 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -109,7 +109,7 @@ If you add the following when you register your services Ocelot will attempt to .. code-block:: csharp services - .AddOcelot(Configuration) + .AddOcelot() .AddStoreOcelotConfigurationInConsul(); You also need to add the following to your configuration.json. This is how Ocelot diff --git a/docs/features/raft.rst b/docs/features/raft.rst index 45ea59f2..dd0cf031 100644 --- a/docs/features/raft.rst +++ b/docs/features/raft.rst @@ -12,7 +12,7 @@ In order to enable Rafty in Ocelot you must make the following changes to your S public virtual void ConfigureServices(IServiceCollection services) { services - .AddOcelot(Configuration) + .AddOcelot() .AddAdministration("/administration", "secret") .AddRafty(); } diff --git a/docs/features/tracing.rst b/docs/features/tracing.rst index a30ea741..0a896d45 100644 --- a/docs/features/tracing.rst +++ b/docs/features/tracing.rst @@ -12,7 +12,7 @@ In your ConfigureServices method .. code-block:: csharp services - .AddOcelot(Configuration) + .AddOcelot() .AddOpenTracing(option => { //this is the url that the butterfly collector server is running on... diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs index 8274af2f..7e78b3aa 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs @@ -11,11 +11,11 @@ namespace Ocelot.Configuration.Repository { public class ConsulFileConfigurationPoller : IDisposable { - private IOcelotLogger _logger; - private IFileConfigurationRepository _repo; - private IFileConfigurationSetter _setter; + private readonly IOcelotLogger _logger; + private readonly IFileConfigurationRepository _repo; + private readonly IFileConfigurationSetter _setter; private string _previousAsJson; - private Timer _timer; + private readonly Timer _timer; private bool _polling; public ConsulFileConfigurationPoller(IOcelotLoggerFactory factory, IFileConfigurationRepository repo, IFileConfigurationSetter setter) @@ -77,4 +77,4 @@ namespace Ocelot.Configuration.Repository _timer.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 3dde5d54..f4192713 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,3 +1,4 @@ +using Butterfly.Client.Tracing; using Microsoft.Extensions.Options; using Ocelot.Middleware.Multiplexer; @@ -146,10 +147,14 @@ namespace Ocelot.DependencyInjection //these get picked out later and added to http request _provider = new DelegatingHandlerHandlerProvider(); - _services.TryAddSingleton(_provider); - _services.AddTransient(); + _services.TryAddSingleton(_provider); _services.TryAddSingleton(); _services.TryAddSingleton(); + _services.AddSingleton(); + + // We add this here so that we can always inject something into the factory for IoC.. + _services.AddSingleton(); + } public IOcelotAdministrationBuilder AddAdministration(string path, string secret) @@ -192,7 +197,8 @@ namespace Ocelot.DependencyInjection public IOcelotBuilder AddOpenTracing(Action settings) { - _services.AddTransient(); + // Earlier we add FakeServiceTracer and need to remove it here before we add butterfly + _services.RemoveAll(); _services.AddButterfly(settings); return this; } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 9dbb7c22..541a8e93 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -173,23 +173,34 @@ var ocelotConfigurationRepository = (IOcelotConfigurationRepository) builder.ApplicationServices.GetService( typeof(IOcelotConfigurationRepository)); + var ocelotConfigurationCreator = (IOcelotConfigurationCreator) builder.ApplicationServices.GetService( typeof(IOcelotConfigurationCreator)); var fileConfigFromConsul = await consulFileConfigRepo.Get(); + if (fileConfigFromConsul.Data == null) { config = await setter.Set(fileConfig.Value); + var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); } else { var ocelotConfig = await ocelotConfigurationCreator.Create(fileConfigFromConsul.Data); + if(ocelotConfig.IsError) { return new ErrorResponse(ocelotConfig.Errors); } + config = await ocelotConfigurationRepository.AddOrReplace(ocelotConfig.Data); + + if (config.IsError) + { + return new ErrorResponse(config.Errors); + } + //todo - this starts the poller if it has been registered...please this is so bad. var hack = builder.ApplicationServices.GetService(typeof(ConsulFileConfigurationPoller)); } diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index eb4ce773..62552bed 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -23,7 +23,7 @@ True - + diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs index c6267123..468b6013 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs @@ -1,3 +1,4 @@ +using System; using System.Net.Http; using Ocelot.Configuration; using Ocelot.Logging; @@ -8,17 +9,17 @@ namespace Ocelot.Requester { public class DelegatingHandlerHandlerProviderFactory : IDelegatingHandlerHandlerProviderFactory { - private readonly ITracingHandler _tracingHandler; + private readonly ITracingHandlerFactory _factory; private readonly IOcelotLoggerFactory _loggerFactory; private readonly IDelegatingHandlerHandlerProvider _allRoutesProvider; private readonly IQosProviderHouse _qosProviderHouse; public DelegatingHandlerHandlerProviderFactory(IOcelotLoggerFactory loggerFactory, - IDelegatingHandlerHandlerProvider allRoutesProvider, - ITracingHandler tracingHandler, + IDelegatingHandlerHandlerProvider allRoutesProvider, + ITracingHandlerFactory factory, IQosProviderHouse qosProviderHouse) { - _tracingHandler = tracingHandler; + _factory = factory; _loggerFactory = loggerFactory; _allRoutesProvider = allRoutesProvider; _qosProviderHouse = qosProviderHouse; @@ -37,7 +38,7 @@ namespace Ocelot.Requester if (request.HttpHandlerOptions.UseTracing) { - provider.Add(() => (DelegatingHandler)_tracingHandler); + provider.Add(() => (DelegatingHandler)_factory.Get()); } if (request.IsQos) diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 9c832b1d..2d3a0f36 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -26,8 +26,10 @@ namespace Ocelot.Requester { var provider = _house.Get(request); + var handlers = provider.Data.Get(); + //todo handle error - provider.Data.Get() + handlers .Select(handler => handler) .Reverse() .ToList() diff --git a/src/Ocelot/Requester/ITracingHandlerFactory.cs b/src/Ocelot/Requester/ITracingHandlerFactory.cs new file mode 100644 index 00000000..6f0f6619 --- /dev/null +++ b/src/Ocelot/Requester/ITracingHandlerFactory.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Requester +{ + public interface ITracingHandlerFactory + { + ITracingHandler Get(); + } +} diff --git a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs index ff588fe8..e2658c7f 100644 --- a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs +++ b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs @@ -12,11 +12,6 @@ namespace Ocelot.Requester { } - public class NoTracingHandler : DelegatingHandler, ITracingHandler - { - - } - public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler { private readonly IServiceTracer _tracer; diff --git a/src/Ocelot/Requester/TracingHandlerFactory.cs b/src/Ocelot/Requester/TracingHandlerFactory.cs new file mode 100644 index 00000000..5cb72a79 --- /dev/null +++ b/src/Ocelot/Requester/TracingHandlerFactory.cs @@ -0,0 +1,32 @@ +using Butterfly.Client.Tracing; +using Butterfly.OpenTracing; + +namespace Ocelot.Requester +{ + public class TracingHandlerFactory : ITracingHandlerFactory + { + private readonly IServiceTracer _tracer; + + public TracingHandlerFactory(IServiceTracer tracer) + { + _tracer = tracer; + } + + public ITracingHandler Get() + { + return new OcelotHttpTracingHandler(_tracer); + } + } + + public class FakeServiceTracer : IServiceTracer + { + public ITracer Tracer { get; } + public string ServiceName { get; } + public string Environment { get; } + public string Identity { get; } + public ISpan Start(ISpanBuilder spanBuilder) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs new file mode 100644 index 00000000..84346985 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs @@ -0,0 +1,225 @@ +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 +{ + 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; + + public ButterflyTracingTests() + { + _steps = new Steps(); + } + + [Fact] + public void should_forward_tracing_information_from_ocelot_and_downstream_services() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/values", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51887, + } + }, + UpstreamPathTemplate = "/api001/values", + UpstreamHttpMethod = new List { "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 + { + new FileHostAndPort + { + Host = "localhost", + Port = 51888, + } + }, + UpstreamPathTemplate = "/api002/values", + UpstreamHttpMethod = new List { "Get" }, + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + }, + QoSOptions = new FileQoSOptions + { + ExceptionsAllowedBeforeBreaking = 3, + DurationOfBreak = 10, + TimeoutValue = 5000 + } + } + } + }; + + var butterflyUrl = "http://localhost:9618"; + + this.Given(x => GivenServiceOneIsRunning("http://localhost:51887", "/api/values", 200, "Hello from Laura", butterflyUrl)) + .And(x => GivenServiceTwoIsRunning("http://localhost:51888", "/api/values", 200, "Hello from Tom", butterflyUrl)) + .And(x => GivenFakeButterfly(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(5000).Until(() => _butterflyCalled == 4); + + commandOnAllStateMachines.ShouldBeTrue(); + } + + 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(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index c95d6323..db617718 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -4,13 +4,10 @@ using System.Diagnostics; using System.IO; using System.Net; using System.Text; -using System.Threading; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; using TestStack.BDDfy; using Xunit; @@ -275,7 +272,11 @@ namespace Ocelot.AcceptanceTests private void GivenIWaitForTheConfigToReplicateToOcelot() { - Thread.Sleep(10000); + var stopWatch = Stopwatch.StartNew(); + while (stopWatch.ElapsedMilliseconds < 10000) + { + //do nothing! + } } private void GivenTheConsulConfigurationIs(FileConfiguration config) diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index ab7ae7ce..396eb403 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -1,53 +1,48 @@ - - - - 0.0.0-dev - netcoreapp2.0 - 2.0.0 - Ocelot.AcceptanceTests - Exe - Ocelot.AcceptanceTests - true - osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 - false - false - false - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + 0.0.0-dev + netcoreapp2.0 + 2.0.0 + Ocelot.AcceptanceTests + Exe + Ocelot.AcceptanceTests + true + osx.10.11-x64;osx.10.12-x64;win7-x64;win10-x64 + false + false + false + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index b7f3e6ab..6a6fa3c4 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -26,6 +26,8 @@ using Ocelot.ServiceDiscovery; using Shouldly; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; using Ocelot.AcceptanceTests.Caching; +using Butterfly.Client.AspNetCore; +using Butterfly.Client.Tracing; namespace Ocelot.AcceptanceTests { @@ -105,6 +107,49 @@ namespace Ocelot.AcceptanceTests _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: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + 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(); + } +/* + public void GivenIHaveAddedXForwardedForHeader(string value) + { + _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Forwarded-For", value); + }*/ + public void GivenOcelotIsRunningWithMiddleareBeforePipeline(Func callback) { _webHostBuilder = new WebHostBuilder(); diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index 8cb4e917..4e728d3d 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -25,25 +25,24 @@ namespace Ocelot.ManualTest .AddEnvironmentVariables(); }) .ConfigureServices(s => { - - s.AddAuthentication() + s.AddAuthentication() .AddJwtBearer("TestKey", x => { x.Authority = "test"; x.Audience = "test"; }); - s.AddOcelot() - .AddCacheManager(x => - { - x.WithDictionaryHandle(); - }) - .AddOpenTracing(option => - { - option.CollectorUrl = "http://localhost:9618"; - option.Service = "Ocelot.ManualTest"; - }) - .AddAdministration("/administration", "secret"); + s.AddOcelot() + .AddCacheManager(x => + { + x.WithDictionaryHandle(); + }) + .AddOpenTracing(option => + { + option.CollectorUrl = "http://localhost:9618"; + option.Service = "Ocelot.ManualTest"; + }) + .AddAdministration("/administration", "secret"); }) .ConfigureLogging((hostingContext, logging) => { diff --git a/test/Ocelot.ManualTest/configuration.json b/test/Ocelot.ManualTest/configuration.json index 98ce38b8..ba34d4e5 100644 --- a/test/Ocelot.ManualTest/configuration.json +++ b/test/Ocelot.ManualTest/configuration.json @@ -8,13 +8,13 @@ "DownstreamHostAndPorts": [ { "Host": "localhost", - "Port": 5001 + "Port": 5007 } ], "HttpHandlerOptions": { "AllowAutoRedirect": true, "UseCookieContainer": true, - "UseTracing": false + "UseTracing": true } }, { diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index 7993644f..fcc2c7e2 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -50,6 +50,7 @@ + diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs index 8f861260..e6942681 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs @@ -23,13 +23,15 @@ namespace Ocelot.UnitTests.Requester private Response _provider; private readonly Mock _allRoutesProvider; private readonly Mock _qosProviderHouse; + private readonly Mock _tracingFactory; public DelegatingHandlerHandlerProviderFactoryTests() { + _tracingFactory = new Mock(); _qosProviderHouse = new Mock(); _allRoutesProvider = new Mock(); _loggerFactory = new Mock(); - _factory = new DelegatingHandlerHandlerProviderFactory(_loggerFactory.Object, _allRoutesProvider.Object, null, _qosProviderHouse.Object); + _factory = new DelegatingHandlerHandlerProviderFactory(_loggerFactory.Object, _allRoutesProvider.Object, _tracingFactory.Object, _qosProviderHouse.Object); } private void GivenTheQosProviderHouseReturns(Response qosProvider) diff --git a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs new file mode 100644 index 00000000..e8196966 --- /dev/null +++ b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs @@ -0,0 +1,27 @@ +using Butterfly.Client.Tracing; +using Moq; +using Ocelot.Requester; +using Shouldly; +using Xunit; + +namespace Ocelot.UnitTests.Requester +{ + public class TracingHandlerFactoryTests + { + private TracingHandlerFactory _factory; + private Mock _tracer; + + public TracingHandlerFactoryTests() + { + _tracer = new Mock(); + _factory = new TracingHandlerFactory(_tracer.Object); + } + + [Fact] + public void should_return() + { + var handler = _factory.Get(); + handler.ShouldBeOfType(); + } + } +} \ No newline at end of file