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
This commit is contained in:
Tom Pallister 2018-03-01 12:58:36 +00:00 committed by GitHub
parent 18c34aa998
commit 9f1fb002c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 449 additions and 100 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -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");
}

View File

@ -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.

View File

@ -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

View File

@ -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();
}

View File

@ -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...

View File

@ -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)

View File

@ -1,3 +1,4 @@
using Butterfly.Client.Tracing;
using Microsoft.Extensions.Options;
using Ocelot.Middleware.Multiplexer;
@ -147,9 +148,13 @@ namespace Ocelot.DependencyInjection
//these get picked out later and added to http request
_provider = new DelegatingHandlerHandlerProvider();
_services.TryAddSingleton<IDelegatingHandlerHandlerProvider>(_provider);
_services.AddTransient<ITracingHandler, NoTracingHandler>();
_services.TryAddSingleton<IMultiplexer, Multiplexer>();
_services.TryAddSingleton<IResponseAggregator, SimpleJsonResponseAggregator>();
_services.AddSingleton<ITracingHandlerFactory, TracingHandlerFactory>();
// We add this here so that we can always inject something into the factory for IoC..
_services.AddSingleton<IServiceTracer, FakeServiceTracer>();
}
public IOcelotAdministrationBuilder AddAdministration(string path, string secret)
@ -192,7 +197,8 @@ namespace Ocelot.DependencyInjection
public IOcelotBuilder AddOpenTracing(Action<ButterflyOptions> settings)
{
_services.AddTransient<ITracingHandler, OcelotHttpTracingHandler>();
// Earlier we add FakeServiceTracer and need to remove it here before we add butterfly
_services.RemoveAll<IServiceTracer>();
_services.AddButterfly(settings);
return this;
}

View File

@ -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));
}

View File

@ -23,7 +23,7 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.5" />
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
<PackageReference Include="FluentValidation" Version="7.2.1" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="2.1.0" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />

View File

@ -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,
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)

View File

@ -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()

View File

@ -0,0 +1,7 @@
namespace Ocelot.Requester
{
public interface ITracingHandlerFactory
{
ITracingHandler Get();
}
}

View File

@ -12,11 +12,6 @@ namespace Ocelot.Requester
{
}
public class NoTracingHandler : DelegatingHandler, ITracingHandler
{
}
public class OcelotHttpTracingHandler : DelegatingHandler, ITracingHandler
{
private readonly IServiceTracer _tracer;

View File

@ -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();
}
}
}

View File

@ -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<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 = 51888,
}
},
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 => 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();
}
}
}

View File

@ -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)

View File

@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>0.0.0-dev</VersionPrefix>
<TargetFramework>netcoreapp2.0</TargetFramework>
@ -13,22 +12,18 @@
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>
<ItemGroup>
<None Update="configuration.json;appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Ocelot\Ocelot.csproj" />
<ProjectReference Include="..\Ocelot.ManualTest\Ocelot.ManualTest.csproj" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CacheManager.Serialization.Json" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
@ -48,6 +43,6 @@
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
<PackageReference Include="Consul" Version="0.7.2.3" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
</ItemGroup>
</Project>

View File

@ -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<T>(Func<object, Task> callback)
{
_webHostBuilder = new WebHostBuilder();

View File

@ -25,7 +25,6 @@ namespace Ocelot.ManualTest
.AddEnvironmentVariables();
})
.ConfigureServices(s => {
s.AddAuthentication()
.AddJwtBearer("TestKey", x =>
{

View File

@ -8,13 +8,13 @@
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 5001
"Port": 5007
}
],
"HttpHandlerOptions": {
"AllowAutoRedirect": true,
"UseCookieContainer": true,
"UseTracing": false
"UseTracing": true
}
},
{

View File

@ -50,6 +50,7 @@
<PackageReference Include="Shouldly" Version="3.0.0-beta0003" />
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
</ItemGroup>
</Project>

View File

@ -23,13 +23,15 @@ namespace Ocelot.UnitTests.Requester
private Response<IDelegatingHandlerHandlerProvider> _provider;
private readonly Mock<IDelegatingHandlerHandlerProvider> _allRoutesProvider;
private readonly Mock<IQosProviderHouse> _qosProviderHouse;
private readonly Mock<ITracingHandlerFactory> _tracingFactory;
public DelegatingHandlerHandlerProviderFactoryTests()
{
_tracingFactory = new Mock<ITracingHandlerFactory>();
_qosProviderHouse = new Mock<IQosProviderHouse>();
_allRoutesProvider = new Mock<IDelegatingHandlerHandlerProvider>();
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_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<IQoSProvider> qosProvider)

View File

@ -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<IServiceTracer> _tracer;
public TracingHandlerFactoryTests()
{
_tracer = new Mock<IServiceTracer>();
_factory = new TracingHandlerFactory(_tracer.Object);
}
[Fact]
public void should_return()
{
var handler = _factory.Get();
handler.ShouldBeOfType<OcelotHttpTracingHandler>();
}
}
}