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
*.userosscache
*.sln.docstates
.DS_Store
# User-specific files (MonoDevelop/Xamarin Studio)
*.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**
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
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}**
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
{
private readonly string _configFilePath;
private readonly string _environmentFilePath;
private readonly string _ocelotFilePath;
private static readonly object _lock = new object();
private const string ConfigurationFileName = "ocelot";
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()
@ -26,7 +27,7 @@ namespace Ocelot.Configuration.Repository
lock(_lock)
{
jsonConfiguration = System.IO.File.ReadAllText(_configFilePath);
jsonConfiguration = System.IO.File.ReadAllText(_environmentFilePath);
}
var fileConfiguration = JsonConvert.DeserializeObject<FileConfiguration>(jsonConfiguration);
@ -40,12 +41,19 @@ namespace Ocelot.Configuration.Repository
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());

View File

@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;
using System.Diagnostics;
using DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
@ -115,22 +116,32 @@
var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository));
internalConfigRepo.AddOrReplace(internalConfig.Data);
var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter));
var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository));
var adminPath = (IAdministrationPath)builder.ApplicationServices.GetService(typeof(IAdministrationPath));
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);
}
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);
}
return GetOcelotConfigAndReturn(internalConfigRepo);
}
private static bool AdministrationApiInUse(IAdministrationPath adminPath)
{
return adminPath.GetType() != typeof(NullAdministrationPath);
}
private static async Task SetFileConfigInConsul(IApplicationBuilder builder,
IFileConfigurationRepository fileConfigRepo, IOptions<FileConfiguration> fileConfig,
IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo)
@ -179,8 +190,7 @@
private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptions<FileConfiguration> fileConfig)
{
Response response;
response = await fileConfigSetter.Set(fileConfig.Value);
var response = await fileConfigSetter.Set(fileConfig.Value);
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
{
using Configuration.Repository;
using Microsoft.Net.Http.Headers;
using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue;
@ -928,5 +929,35 @@ namespace Ocelot.AcceptanceTests
var content = await response.Content.ReadAsStringAsync();
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))
.When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration"))
.And(x => ThenTheResponseShouldBe(updatedConfiguration))
.And(_ => ThenTheConfigurationIsSavedCorrectly(updatedConfiguration))
.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]
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 Newtonsoft.Json;
using System.IO;
using System.Threading;
using Microsoft.AspNetCore.Hosting;
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 string _configurationPath;
private string _environmentSpecificPath;
private string _ocelotJsonPath;
private FileConfiguration _result;
private FileConfiguration _fileConfiguration;
// 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
// tests but whatever...
// cant pick it up if they run in parralel..and the semaphore stops them running at the same time...sigh
// these are not really unit tests but whatever...
private string _environmentName = "DEV.DEV";
private static SemaphoreSlim _semaphore;
public DiskFileConfigurationRepositoryTests()
{
_semaphore = new SemaphoreSlim(1, 1);
_semaphore.Wait();
_hostingEnvironment = new Mock<IHostingEnvironment>();
_hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName);
_repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object);
}
@ -79,6 +85,33 @@ namespace Ocelot.UnitTests.Configuration
.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()
{
_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)
{
_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);
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)
{
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);
resultText.ShouldBe(expectedText);
}
@ -238,5 +278,10 @@ namespace Ocelot.UnitTests.Configuration
ReRoutes = reRoutes
};
}
public void Dispose()
{
_semaphore.Release();
}
}
}