Feature/fix #463 (#506)

* #463 save both files

* #463 made it so we dont save to disk on startup unless using admin api
This commit is contained in:
Tom Pallister 2018-07-27 23:13:22 +01:00 committed by GitHub
parent 9f4448378a
commit 1817564ea5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 484 additions and 274 deletions

2
.gitignore vendored
View File

@ -6,7 +6,7 @@
*.user *.user
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
.DS_Store
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs

View File

@ -98,12 +98,14 @@ This gets the current Ocelot configuration. It is exactly the same JSON we use t
**POST {adminPath}/configuration** **POST {adminPath}/configuration**
This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples. This overrwrites the existing configuration (should probably be a put!). I reccomend getting your config from the GET endpoint, making any changes and posting it back...simples.
The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up The body of the request is JSON and it is the same format as the FileConfiguration.cs that we use to set up
Ocelot on a file system. Ocelot on a file system.
Please note that if you want to use this API then the process running Ocelot must have permission to write to the disk
where your ocelot.json or ocelot.{environment}.json is located. This is because Ocelot will overwrite them on save.
**DELETE {adminPath}/outputcache/{region}** **DELETE {adminPath}/outputcache/{region}**
This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache. This clears a region of the cache. If you are using a backplane it will clear all instances of the cache! Giving your the ability to run a cluster of Ocelots and cache over all of them in memory and clear them all at the same time / just use a distributed cache.

View File

@ -9,15 +9,16 @@ namespace Ocelot.Configuration.Repository
{ {
public class DiskFileConfigurationRepository : IFileConfigurationRepository public class DiskFileConfigurationRepository : IFileConfigurationRepository
{ {
private readonly string _configFilePath; private readonly string _environmentFilePath;
private readonly string _ocelotFilePath;
private static readonly object _lock = new object(); private static readonly object _lock = new object();
private const string ConfigurationFileName = "ocelot"; private const string ConfigurationFileName = "ocelot";
public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment) public DiskFileConfigurationRepository(IHostingEnvironment hostingEnvironment)
{ {
_configFilePath = $"{AppContext.BaseDirectory}/{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json"; _environmentFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}{(string.IsNullOrEmpty(hostingEnvironment.EnvironmentName) ? string.Empty : ".")}{hostingEnvironment.EnvironmentName}.json";
_ocelotFilePath = $"{AppContext.BaseDirectory}{ConfigurationFileName}.json";
} }
public Task<Response<FileConfiguration>> Get() public Task<Response<FileConfiguration>> Get()
@ -26,7 +27,7 @@ namespace Ocelot.Configuration.Repository
lock(_lock) lock(_lock)
{ {
jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); jsonConfiguration = System.IO.File.ReadAllText(_environmentFilePath);
} }
var fileConfiguration = JsonConvert.DeserializeObject<FileConfiguration>(jsonConfiguration); var fileConfiguration = JsonConvert.DeserializeObject<FileConfiguration>(jsonConfiguration);
@ -40,12 +41,19 @@ namespace Ocelot.Configuration.Repository
lock(_lock) lock(_lock)
{ {
if (System.IO.File.Exists(_configFilePath)) if (System.IO.File.Exists(_environmentFilePath))
{ {
System.IO.File.Delete(_configFilePath); System.IO.File.Delete(_environmentFilePath);
} }
System.IO.File.WriteAllText(_configFilePath, jsonConfiguration); System.IO.File.WriteAllText(_environmentFilePath, jsonConfiguration);
if (System.IO.File.Exists(_ocelotFilePath))
{
System.IO.File.Delete(_ocelotFilePath);
}
System.IO.File.WriteAllText(_ocelotFilePath, jsonConfiguration);
} }
return Task.FromResult<Response>(new OkResponse()); return Task.FromResult<Response>(new OkResponse());

View File

@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Diagnostics; using System.Diagnostics;
using DependencyInjection;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
@ -115,22 +116,32 @@
var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository));
internalConfigRepo.AddOrReplace(internalConfig.Data); internalConfigRepo.AddOrReplace(internalConfig.Data);
var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter));
var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository));
var adminPath = (IAdministrationPath)builder.ApplicationServices.GetService(typeof(IAdministrationPath));
if (UsingConsul(fileConfigRepo)) if (UsingConsul(fileConfigRepo))
{ {
//Lots of jazz happens in here..check it out if you are using consul to store your config.
await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo);
} }
else else if(AdministrationApiInUse(adminPath))
{ {
//We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the
//admin api it works...boy this is getting a spit spags boll.
var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter));
await SetFileConfig(fileConfigSetter, fileConfig); await SetFileConfig(fileConfigSetter, fileConfig);
} }
return GetOcelotConfigAndReturn(internalConfigRepo); return GetOcelotConfigAndReturn(internalConfigRepo);
} }
private static bool AdministrationApiInUse(IAdministrationPath adminPath)
{
return adminPath.GetType() != typeof(NullAdministrationPath);
}
private static async Task SetFileConfigInConsul(IApplicationBuilder builder, private static async Task SetFileConfigInConsul(IApplicationBuilder builder,
IFileConfigurationRepository fileConfigRepo, IOptions<FileConfiguration> fileConfig, IFileConfigurationRepository fileConfigRepo, IOptions<FileConfiguration> fileConfig,
IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
@ -179,8 +190,7 @@
private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions<FileConfiguration> fileConfig) private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions<FileConfiguration> fileConfig)
{ {
Response response; var response = await fileConfigSetter.Set(fileConfig.Value);
response = await fileConfigSetter.Set(fileConfig.Value);
if (IsError(response)) if (IsError(response))
{ {

View File

@ -0,0 +1,100 @@
namespace Ocelot.AcceptanceTests
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Configuration.Repository;
using Microsoft.AspNetCore.Http;
using Ocelot.Configuration.File;
using Responses;
using TestStack.BDDfy;
using Xunit;
public class StartupTests : IDisposable
{
private readonly Steps _steps;
private readonly ServiceHandler _serviceHandler;
private string _downstreamPath;
public StartupTests()
{
_serviceHandler = new ServiceHandler();
_steps = new Steps();
}
[Fact]
public void should_not_try_and_write_to_disk_on_startup_when_not_using_admin_api()
{
var configuration = new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamPathTemplate = "/",
DownstreamScheme = "http",
DownstreamHostAndPorts = new List<FileHostAndPort>
{
new FileHostAndPort
{
Host = "localhost",
Port = 52179,
}
},
UpstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "Get" },
}
}
};
var fakeRepo = new FakeFileConfigurationRepository();
this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52179", "/", 200, "Hello from Laura"))
.And(x => _steps.GivenThereIsAConfiguration(configuration))
.And(x => _steps.GivenOcelotIsRunningWithBlowingUpDiskRepo(fakeRepo))
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
.BDDfy();
}
private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody)
{
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context =>
{
_downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value;
if (_downstreamPath != 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);
}
});
}
public void Dispose()
{
_serviceHandler?.Dispose();
_steps.Dispose();
}
class FakeFileConfigurationRepository : IFileConfigurationRepository
{
public Task<Response<FileConfiguration>> Get()
{
throw new NotImplementedException();
}
public Task<Response> Set(FileConfiguration fileConfiguration)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -31,6 +31,7 @@ using static Ocelot.Infrastructure.Wait;
namespace Ocelot.AcceptanceTests namespace Ocelot.AcceptanceTests
{ {
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;
@ -928,5 +929,35 @@ namespace Ocelot.AcceptanceTests
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
content.ShouldBe(expectedBody); content.ShouldBe(expectedBody);
} }
public void GivenOcelotIsRunningWithBlowingUpDiskRepo(IFileConfigurationRepository fake)
{
_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", false, false);
config.AddEnvironmentVariables();
})
.ConfigureServices(s =>
{
s.AddSingleton<IFileConfigurationRepository>(fake);
s.AddOcelot();
})
.Configure(app =>
{
app.UseOcelot().Wait();
});
_ocelotServer = new TestServer(_webHostBuilder);
_ocelotClient = _ocelotServer.CreateClient();
}
} }
} }

View File

@ -276,9 +276,23 @@ namespace Ocelot.IntegrationTests
.And(x => ThenTheResponseShouldBe(updatedConfiguration)) .And(x => ThenTheResponseShouldBe(updatedConfiguration))
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.And(x => ThenTheResponseShouldBe(updatedConfiguration)) .And(x => ThenTheResponseShouldBe(updatedConfiguration))
.And(_ => ThenTheConfigurationIsSavedCorrectly(updatedConfiguration))
.BDDfy(); .BDDfy();
} }
private void ThenTheConfigurationIsSavedCorrectly(FileConfiguration expected)
{
var ocelotJsonPath = $"{AppContext.BaseDirectory}ocelot.json";
var resultText = File.ReadAllText(ocelotJsonPath);
var expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented);
resultText.ShouldBe(expectedText);
var environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot.Production.json";
resultText = File.ReadAllText(environmentSpecificPath);
expectedText = JsonConvert.SerializeObject(expected, Formatting.Indented);
resultText.ShouldBe(expectedText);
}
[Fact] [Fact]
public void should_get_file_configuration_edit_and_post_updated_version_redirecting_reroute() public void should_get_file_configuration_edit_and_post_updated_version_redirecting_reroute()
{ {

View File

@ -9,24 +9,30 @@ namespace Ocelot.UnitTests.Configuration
using Xunit; using Xunit;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.IO; using System.IO;
using System.Threading;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Ocelot.Configuration.Repository; using Ocelot.Configuration.Repository;
public class DiskFileConfigurationRepositoryTests public class DiskFileConfigurationRepositoryTests : IDisposable
{ {
private readonly Mock<IHostingEnvironment> _hostingEnvironment = new Mock<IHostingEnvironment>(); private readonly Mock<IHostingEnvironment> _hostingEnvironment;
private IFileConfigurationRepository _repo; private IFileConfigurationRepository _repo;
private string _configurationPath; private string _environmentSpecificPath;
private string _ocelotJsonPath;
private FileConfiguration _result; private FileConfiguration _result;
private FileConfiguration _fileConfiguration; private FileConfiguration _fileConfiguration;
// This is a bit dirty and it is dev.dev so that the ConfigurationBuilderExtensionsTests // This is a bit dirty and it is dev.dev so that the ConfigurationBuilderExtensionsTests
// cant pick it up if they run in parralel..sigh these are not really unit // cant pick it up if they run in parralel..and the semaphore stops them running at the same time...sigh
// tests but whatever... // these are not really unit tests but whatever...
private string _environmentName = "DEV.DEV"; private string _environmentName = "DEV.DEV";
private static SemaphoreSlim _semaphore;
public DiskFileConfigurationRepositoryTests() public DiskFileConfigurationRepositoryTests()
{ {
_semaphore = new SemaphoreSlim(1, 1);
_semaphore.Wait();
_hostingEnvironment = new Mock<IHostingEnvironment>();
_hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName);
_repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object);
} }
@ -79,6 +85,33 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_set_environment_file_configuration_and_ocelot_file_configuration()
{
var config = FakeFileConfigurationForSet();
this.Given(_ => GivenIHaveAConfiguration(config))
.And(_ => GivenTheConfigurationIs(config))
.And(_ => GivenTheUserAddedOcelotJson())
.When(_ => WhenISetTheConfiguration())
.Then(_ => ThenTheConfigurationIsStoredAs(config))
.And(_ => ThenTheConfigurationJsonIsIndented(config))
.Then(_ => ThenTheOcelotJsonIsStoredAs(config))
.BDDfy();
}
private void GivenTheUserAddedOcelotJson()
{
_ocelotJsonPath = $"{AppContext.BaseDirectory}/ocelot.json";
if (File.Exists(_ocelotJsonPath))
{
File.Delete(_ocelotJsonPath);
}
File.WriteAllText(_ocelotJsonPath, "Doesnt matter");
}
private void GivenTheEnvironmentNameIsUnavailable() private void GivenTheEnvironmentNameIsUnavailable()
{ {
_environmentName = null; _environmentName = null;
@ -119,25 +152,32 @@ namespace Ocelot.UnitTests.Configuration
} }
} }
private void ThenTheOcelotJsonIsStoredAs(FileConfiguration expecteds)
{
var resultText = File.ReadAllText(_ocelotJsonPath);
var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented);
resultText.ShouldBe(expectedText);
}
private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) private void GivenTheConfigurationIs(FileConfiguration fileConfiguration)
{ {
_configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; _environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json";
var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented);
if (File.Exists(_configurationPath)) if (File.Exists(_environmentSpecificPath))
{ {
File.Delete(_configurationPath); File.Delete(_environmentSpecificPath);
} }
File.WriteAllText(_configurationPath, jsonConfiguration); File.WriteAllText(_environmentSpecificPath, jsonConfiguration);
} }
private void ThenTheConfigurationJsonIsIndented(FileConfiguration expecteds) private void ThenTheConfigurationJsonIsIndented(FileConfiguration expecteds)
{ {
var path = !string.IsNullOrEmpty(_configurationPath) ? _configurationPath : _configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; var path = !string.IsNullOrEmpty(_environmentSpecificPath) ? _environmentSpecificPath : _environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json";
var resultText = File.ReadAllText(_configurationPath); var resultText = File.ReadAllText(path);
var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented);
resultText.ShouldBe(expectedText); resultText.ShouldBe(expectedText);
} }
@ -238,5 +278,10 @@ namespace Ocelot.UnitTests.Configuration
ReRoutes = reRoutes ReRoutes = reRoutes
}; };
} }
public void Dispose()
{
_semaphore.Release();
}
} }
} }