mirror of
https://github.com/nsnail/Ocelot.git
synced 2025-04-22 06:22:50 +08:00
This commit is contained in:
parent
afe322693e
commit
a91235b405
@ -1,97 +0,0 @@
|
|||||||
namespace Ocelot.Configuration.Repository
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Consul;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Ocelot.Configuration.File;
|
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.Responses;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
|
|
||||||
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
|
|
||||||
{
|
|
||||||
private readonly IConsulClient _consul;
|
|
||||||
private readonly string _configurationKey;
|
|
||||||
private readonly Cache.IOcelotCache<FileConfiguration> _cache;
|
|
||||||
private readonly IOcelotLogger _logger;
|
|
||||||
|
|
||||||
public ConsulFileConfigurationRepository(
|
|
||||||
Cache.IOcelotCache<FileConfiguration> cache,
|
|
||||||
IInternalConfigurationRepository repo,
|
|
||||||
IConsulClientFactory factory,
|
|
||||||
IOcelotLoggerFactory loggerFactory)
|
|
||||||
{
|
|
||||||
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
|
|
||||||
_cache = cache;
|
|
||||||
|
|
||||||
var internalConfig = repo.Get();
|
|
||||||
|
|
||||||
_configurationKey = "InternalConfiguration";
|
|
||||||
|
|
||||||
string token = null;
|
|
||||||
|
|
||||||
if (!internalConfig.IsError)
|
|
||||||
{
|
|
||||||
token = internalConfig.Data.ServiceProviderConfiguration.Token;
|
|
||||||
_configurationKey = !string.IsNullOrEmpty(internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey) ?
|
|
||||||
internalConfig.Data.ServiceProviderConfiguration.ConfigurationKey : _configurationKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = new ConsulRegistryConfiguration(internalConfig.Data.ServiceProviderConfiguration.Host,
|
|
||||||
internalConfig.Data.ServiceProviderConfiguration.Port, _configurationKey, token);
|
|
||||||
|
|
||||||
_consul = factory.Get(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Response<FileConfiguration>> Get()
|
|
||||||
{
|
|
||||||
var config = _cache.Get(_configurationKey, _configurationKey);
|
|
||||||
|
|
||||||
if (config != null)
|
|
||||||
{
|
|
||||||
return new OkResponse<FileConfiguration>(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
var queryResult = await _consul.KV.Get(_configurationKey);
|
|
||||||
|
|
||||||
if (queryResult.Response == null)
|
|
||||||
{
|
|
||||||
return new OkResponse<FileConfiguration>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes = queryResult.Response.Value;
|
|
||||||
|
|
||||||
var json = Encoding.UTF8.GetString(bytes);
|
|
||||||
|
|
||||||
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
|
||||||
|
|
||||||
return new OkResponse<FileConfiguration>(consulConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Response> Set(FileConfiguration ocelotConfiguration)
|
|
||||||
{
|
|
||||||
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(json);
|
|
||||||
|
|
||||||
var kvPair = new KVPair(_configurationKey)
|
|
||||||
{
|
|
||||||
Value = bytes
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await _consul.KV.Put(kvPair);
|
|
||||||
|
|
||||||
if (result.Response)
|
|
||||||
{
|
|
||||||
_cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
|
|
||||||
|
|
||||||
return new OkResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,8 +10,8 @@ namespace Ocelot.DependencyInjection
|
|||||||
public interface IOcelotBuilder
|
public interface IOcelotBuilder
|
||||||
{
|
{
|
||||||
IServiceCollection Services { get; }
|
IServiceCollection Services { get; }
|
||||||
|
|
||||||
IConfiguration Configuration { get; }
|
IConfiguration Configuration { get; }
|
||||||
IOcelotBuilder AddStoreOcelotConfigurationInConsul();
|
|
||||||
|
|
||||||
IOcelotAdministrationBuilder AddAdministration(string path, string secret);
|
IOcelotAdministrationBuilder AddAdministration(string path, string secret);
|
||||||
|
|
||||||
@ -22,6 +22,7 @@ namespace Ocelot.DependencyInjection
|
|||||||
|
|
||||||
IOcelotBuilder AddSingletonDefinedAggregator<T>()
|
IOcelotBuilder AddSingletonDefinedAggregator<T>()
|
||||||
where T : class, IDefinedAggregator;
|
where T : class, IDefinedAggregator;
|
||||||
|
|
||||||
IOcelotBuilder AddTransientDefinedAggregator<T>()
|
IOcelotBuilder AddTransientDefinedAggregator<T>()
|
||||||
where T : class, IDefinedAggregator;
|
where T : class, IDefinedAggregator;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ namespace Ocelot.DependencyInjection
|
|||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using Ocelot.Infrastructure;
|
using Ocelot.Infrastructure;
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Ocelot.Middleware.Multiplexer;
|
using Ocelot.Middleware.Multiplexer;
|
||||||
using ServiceDiscovery.Providers;
|
using ServiceDiscovery.Providers;
|
||||||
using Steeltoe.Common.Discovery;
|
using Steeltoe.Common.Discovery;
|
||||||
@ -148,7 +147,6 @@ namespace Ocelot.DependencyInjection
|
|||||||
Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();
|
Services.TryAddSingleton<IFileConfigurationPollerOptions, InMemoryFileConfigurationPollerOptions>();
|
||||||
Services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
Services.TryAddSingleton<IAddHeadersToResponse, AddHeadersToResponse>();
|
||||||
Services.TryAddSingleton<IPlaceholders, Placeholders>();
|
Services.TryAddSingleton<IPlaceholders, Placeholders>();
|
||||||
Services.TryAddSingleton<IConsulClientFactory, ConsulClientFactory>();
|
|
||||||
Services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
|
Services.TryAddSingleton<IResponseAggregatorFactory, InMemoryResponseAggregatorFactory>();
|
||||||
Services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
|
Services.TryAddSingleton<IDefinedAggregatorProvider, ServiceLocatorDefinedAggregatorProvider>();
|
||||||
Services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
|
Services.TryAddSingleton<IDownstreamRequestCreator, DownstreamRequestCreator>();
|
||||||
@ -217,13 +215,6 @@ namespace Ocelot.DependencyInjection
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOcelotBuilder AddStoreOcelotConfigurationInConsul()
|
|
||||||
{
|
|
||||||
Services.AddHostedService<FileConfigurationPoller>();
|
|
||||||
Services.AddSingleton<IFileConfigurationRepository, ConsulFileConfigurationRepository>();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddIdentityServer(Action<IdentityServerAuthenticationOptions> configOptions)
|
private void AddIdentityServer(Action<IdentityServerAuthenticationOptions> configOptions)
|
||||||
{
|
{
|
||||||
Services
|
Services
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Consul;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
|
|
||||||
namespace Ocelot.Infrastructure.Consul
|
|
||||||
{
|
|
||||||
public class ConsulClientFactory : IConsulClientFactory
|
|
||||||
{
|
|
||||||
public IConsulClient Get(ConsulRegistryConfiguration config)
|
|
||||||
{
|
|
||||||
return new ConsulClient(c =>
|
|
||||||
{
|
|
||||||
c.Address = new Uri($"http://{config.Host}:{config.Port}");
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(config?.Token))
|
|
||||||
{
|
|
||||||
c.Token = config.Token;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
using Consul;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
|
|
||||||
namespace Ocelot.Infrastructure.Consul
|
|
||||||
{
|
|
||||||
public interface IConsulClientFactory
|
|
||||||
{
|
|
||||||
IConsulClient Get(ConsulRegistryConfiguration config);
|
|
||||||
}
|
|
||||||
}
|
|
@ -242,7 +242,8 @@
|
|||||||
|
|
||||||
private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo)
|
private static bool UsingConsul(IFileConfigurationRepository fileConfigRepo)
|
||||||
{
|
{
|
||||||
return fileConfigRepo.GetType() == typeof(ConsulFileConfigurationRepository);
|
//todo - remove coupling by string
|
||||||
|
return fileConfigRepo.GetType().Name == "ConsulFileConfigurationRepository";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration)
|
private static void CreateAdministrationArea(IApplicationBuilder builder, IInternalConfiguration configuration)
|
||||||
|
@ -46,7 +46,6 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.0" />
|
||||||
<PackageReference Include="Consul" Version="0.7.2.5" />
|
|
||||||
<PackageReference Include="Polly" Version="6.0.1" />
|
<PackageReference Include="Polly" Version="6.0.1" />
|
||||||
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
||||||
<PackageReference Include="Pivotal.Discovery.ClientCore" Version="2.0.1" />
|
<PackageReference Include="Pivotal.Discovery.ClientCore" Version="2.0.1" />
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
namespace Ocelot.ServiceDiscovery.Configuration
|
|
||||||
{
|
|
||||||
public class ConsulRegistryConfiguration
|
|
||||||
{
|
|
||||||
public ConsulRegistryConfiguration(string host, int port, string keyOfServiceInConsul, string token)
|
|
||||||
{
|
|
||||||
Host = string.IsNullOrEmpty(host) ? "localhost" : host;
|
|
||||||
Port = port > 0 ? port : 8500;
|
|
||||||
KeyOfServiceInConsul = keyOfServiceInConsul;
|
|
||||||
Token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string KeyOfServiceInConsul { get; }
|
|
||||||
public string Host { get; }
|
|
||||||
public int Port { get; }
|
|
||||||
public string Token { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Consul;
|
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Ocelot.Infrastructure.Extensions;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
using Ocelot.Values;
|
|
||||||
|
|
||||||
namespace Ocelot.ServiceDiscovery.Providers
|
|
||||||
{
|
|
||||||
public class ConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
|
|
||||||
{
|
|
||||||
private readonly ConsulRegistryConfiguration _config;
|
|
||||||
private readonly IOcelotLogger _logger;
|
|
||||||
private readonly IConsulClient _consul;
|
|
||||||
private const string VersionPrefix = "version-";
|
|
||||||
|
|
||||||
public ConsulServiceDiscoveryProvider(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory)
|
|
||||||
{;
|
|
||||||
_logger = factory.CreateLogger<ConsulServiceDiscoveryProvider>();
|
|
||||||
|
|
||||||
_config = config;
|
|
||||||
_consul = clientFactory.Get(_config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<Service>> Get()
|
|
||||||
{
|
|
||||||
var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true);
|
|
||||||
|
|
||||||
var services = new List<Service>();
|
|
||||||
|
|
||||||
foreach (var serviceEntry in queryResult.Response)
|
|
||||||
{
|
|
||||||
if (IsValid(serviceEntry))
|
|
||||||
{
|
|
||||||
services.Add(BuildService(serviceEntry));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning($"Unable to use service Address: {serviceEntry.Service.Address} and Port: {serviceEntry.Service.Port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return services.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Service BuildService(ServiceEntry serviceEntry)
|
|
||||||
{
|
|
||||||
return new Service(
|
|
||||||
serviceEntry.Service.Service,
|
|
||||||
new ServiceHostAndPort(serviceEntry.Service.Address, serviceEntry.Service.Port),
|
|
||||||
serviceEntry.Service.ID,
|
|
||||||
GetVersionFromStrings(serviceEntry.Service.Tags),
|
|
||||||
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsValid(ServiceEntry serviceEntry)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(serviceEntry.Service.Address) || serviceEntry.Service.Address.Contains("http://") || serviceEntry.Service.Address.Contains("https://") || serviceEntry.Service.Port <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetVersionFromStrings(IEnumerable<string> strings)
|
|
||||||
{
|
|
||||||
return strings
|
|
||||||
?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
|
|
||||||
.TrimStart(VersionPrefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Consul;
|
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Ocelot.Infrastructure.Extensions;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
using Ocelot.Values;
|
|
||||||
|
|
||||||
namespace Ocelot.ServiceDiscovery.Providers
|
|
||||||
{
|
|
||||||
public class PollingConsulServiceDiscoveryProvider : IServiceDiscoveryProvider
|
|
||||||
{
|
|
||||||
private readonly IOcelotLogger _logger;
|
|
||||||
private readonly IServiceDiscoveryProvider _consulServiceDiscoveryProvider;
|
|
||||||
private readonly Timer _timer;
|
|
||||||
private bool _polling;
|
|
||||||
private List<Service> _services;
|
|
||||||
private string _keyOfServiceInConsul;
|
|
||||||
|
|
||||||
public PollingConsulServiceDiscoveryProvider(int pollingInterval, string keyOfServiceInConsul, IOcelotLoggerFactory factory, IServiceDiscoveryProvider consulServiceDiscoveryProvider)
|
|
||||||
{;
|
|
||||||
_logger = factory.CreateLogger<PollingConsulServiceDiscoveryProvider>();
|
|
||||||
_keyOfServiceInConsul = keyOfServiceInConsul;
|
|
||||||
_consulServiceDiscoveryProvider = consulServiceDiscoveryProvider;
|
|
||||||
_services = new List<Service>();
|
|
||||||
|
|
||||||
_timer = new Timer(async x =>
|
|
||||||
{
|
|
||||||
if(_polling)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_polling = true;
|
|
||||||
await Poll();
|
|
||||||
_polling = false;
|
|
||||||
|
|
||||||
}, null, pollingInterval, pollingInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<Service>> Get()
|
|
||||||
{
|
|
||||||
return Task.FromResult(_services);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Poll()
|
|
||||||
{
|
|
||||||
_services = await _consulServiceDiscoveryProvider.Get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +1,31 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Ocelot.Configuration;
|
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
using Ocelot.ServiceDiscovery.Providers;
|
|
||||||
using Ocelot.Values;
|
|
||||||
|
|
||||||
namespace Ocelot.ServiceDiscovery
|
namespace Ocelot.ServiceDiscovery
|
||||||
{
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.ServiceDiscovery.Configuration;
|
||||||
|
using Ocelot.ServiceDiscovery.Providers;
|
||||||
|
using Ocelot.Values;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Steeltoe.Common.Discovery;
|
using Steeltoe.Common.Discovery;
|
||||||
|
|
||||||
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
|
public class ServiceDiscoveryProviderFactory : IServiceDiscoveryProviderFactory
|
||||||
{
|
{
|
||||||
private readonly IOcelotLoggerFactory _factory;
|
private readonly IOcelotLoggerFactory _factory;
|
||||||
private readonly IConsulClientFactory _consulFactory;
|
|
||||||
private readonly IDiscoveryClient _eurekaClient;
|
private readonly IDiscoveryClient _eurekaClient;
|
||||||
|
private readonly List<ServiceDiscoveryFinderDelegate> _delegates;
|
||||||
|
private readonly IServiceProvider _provider;
|
||||||
|
|
||||||
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory consulFactory, IDiscoveryClient eurekaClient)
|
public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IDiscoveryClient eurekaClient, IServiceProvider provider)
|
||||||
{
|
{
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
_consulFactory = consulFactory;
|
|
||||||
_eurekaClient = eurekaClient;
|
_eurekaClient = eurekaClient;
|
||||||
|
_provider = provider;
|
||||||
|
_delegates = provider
|
||||||
|
.GetServices<ServiceDiscoveryFinderDelegate>()
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
|
public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute)
|
||||||
@ -42,29 +47,27 @@ namespace Ocelot.ServiceDiscovery
|
|||||||
return new ConfigurationServiceProvider(services);
|
return new ConfigurationServiceProvider(services);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration serviceConfig, string serviceName)
|
private IServiceDiscoveryProvider GetServiceDiscoveryProvider(ServiceProviderConfiguration config, string key)
|
||||||
{
|
{
|
||||||
if (serviceConfig.Type?.ToLower() == "servicefabric")
|
if (config.Type?.ToLower() == "servicefabric")
|
||||||
{
|
{
|
||||||
var config = new ServiceFabricConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName);
|
var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, key);
|
||||||
return new ServiceFabricServiceDiscoveryProvider(config);
|
return new ServiceFabricServiceDiscoveryProvider(sfConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceConfig.Type?.ToLower() == "eureka")
|
if (config.Type?.ToLower() == "eureka")
|
||||||
{
|
{
|
||||||
return new EurekaServiceDiscoveryProvider(serviceName, _eurekaClient);
|
return new EurekaServiceDiscoveryProvider(key, _eurekaClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token);
|
// Todo - dont just hardcode this...only expect Consul at the momement so works.
|
||||||
|
var finderDelegate = _delegates.FirstOrDefault();
|
||||||
|
|
||||||
var consulServiceDiscoveryProvider = new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory);
|
var provider = finderDelegate?.Invoke(_provider, config, key);
|
||||||
|
|
||||||
if (serviceConfig.Type?.ToLower() == "pollconsul")
|
return provider;
|
||||||
{
|
|
||||||
return new PollingConsulServiceDiscoveryProvider(serviceConfig.PollingInterval, consulRegistryConfiguration.KeyOfServiceInConsul, _factory, consulServiceDiscoveryProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
return consulServiceDiscoveryProvider;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public delegate IServiceDiscoveryProvider ServiceDiscoveryFinderDelegate(IServiceProvider provider, ServiceProviderConfiguration config, string key);
|
||||||
}
|
}
|
||||||
|
@ -1,469 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using Consul;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Ocelot.Configuration.File;
|
|
||||||
using Shouldly;
|
|
||||||
using TestStack.BDDfy;
|
|
||||||
using Xunit;
|
|
||||||
using static Ocelot.Infrastructure.Wait;
|
|
||||||
|
|
||||||
namespace Ocelot.AcceptanceTests
|
|
||||||
{
|
|
||||||
using Cache;
|
|
||||||
|
|
||||||
public class ConfigurationInConsulTests : IDisposable
|
|
||||||
{
|
|
||||||
private IWebHost _builder;
|
|
||||||
private readonly Steps _steps;
|
|
||||||
private IWebHost _fakeConsulBuilder;
|
|
||||||
private FileConfiguration _config;
|
|
||||||
private List<ServiceEntry> _consulServices;
|
|
||||||
|
|
||||||
public ConfigurationInConsulTests()
|
|
||||||
{
|
|
||||||
_consulServices = new List<ServiceEntry>();
|
|
||||||
_steps = new Steps();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_return_response_200_with_simple_url()
|
|
||||||
{
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 51779,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpstreamPathTemplate = "/",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 9500
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var fakeConsulServiceDiscoveryUrl = "http://localhost:9500";
|
|
||||||
|
|
||||||
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
|
|
||||||
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura"))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_load_configuration_out_of_consul()
|
|
||||||
{
|
|
||||||
var consulPort = 8500;
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
|
|
||||||
var consulConfig = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/status",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 51779,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpstreamPathTemplate = "/cs/status",
|
|
||||||
UpstreamHttpMethod = new List<string> {"Get"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
|
|
||||||
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
|
|
||||||
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura"))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_load_configuration_out_of_consul_if_it_is_changed()
|
|
||||||
{
|
|
||||||
var consulPort = 8506;
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
|
|
||||||
var consulConfig = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/status",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 51780,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpstreamPathTemplate = "/cs/status",
|
|
||||||
UpstreamHttpMethod = new List<string> {"Get"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var secondConsulConfig = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/status",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 51780,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpstreamPathTemplate = "/cs/status/awesome",
|
|
||||||
UpstreamHttpMethod = new List<string> {"Get"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => GivenTheConsulConfigurationIs(consulConfig))
|
|
||||||
.And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, ""))
|
|
||||||
.And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura"))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
|
||||||
.And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status"))
|
|
||||||
.And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.When(x => GivenTheConsulConfigurationIs(secondConsulConfig))
|
|
||||||
.Then(x => ThenTheConfigIsUpdatedInOcelot())
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes_and_rate_limit()
|
|
||||||
{
|
|
||||||
const int consulPort = 8523;
|
|
||||||
const string serviceName = "web";
|
|
||||||
const int downstreamServicePort = 8187;
|
|
||||||
var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}";
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = downstreamServicePort,
|
|
||||||
ID = "web_90_0_2_224_8080",
|
|
||||||
Tags = new[] {"version-v1"}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var consulConfig = new FileConfiguration
|
|
||||||
{
|
|
||||||
DynamicReRoutes = new List<FileDynamicReRoute>
|
|
||||||
{
|
|
||||||
new FileDynamicReRoute
|
|
||||||
{
|
|
||||||
ServiceName = serviceName,
|
|
||||||
RateLimitRule = new FileRateLimitRule()
|
|
||||||
{
|
|
||||||
EnableRateLimiting = true,
|
|
||||||
ClientWhitelist = new List<string>(),
|
|
||||||
Limit = 3,
|
|
||||||
Period = "1s",
|
|
||||||
PeriodTimespan = 1000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
},
|
|
||||||
RateLimitOptions = new FileRateLimitOptions()
|
|
||||||
{
|
|
||||||
ClientIdHeader = "ClientId",
|
|
||||||
DisableRateLimitHeaders = false,
|
|
||||||
QuotaExceededMessage = "",
|
|
||||||
RateLimitCounterPrefix = "",
|
|
||||||
HttpStatusCode = 428
|
|
||||||
},
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura"))
|
|
||||||
.And(x => GivenTheConsulConfigurationIs(consulConfig))
|
|
||||||
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
|
||||||
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something", 2))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(200))
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/web/something",1))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(428))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheConfigIsUpdatedInOcelot()
|
|
||||||
{
|
|
||||||
var result = WaitFor(20000).Until(() => {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome");
|
|
||||||
_steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK);
|
|
||||||
_steps.ThenTheResponseBodyShouldBe("Hello from Laura");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
result.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenTheConsulConfigurationIs(FileConfiguration config)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
|
|
||||||
{
|
|
||||||
foreach(var serviceEntry in serviceEntries)
|
|
||||||
{
|
|
||||||
_consulServices.Add(serviceEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
|
|
||||||
{
|
|
||||||
_fakeConsulBuilder = new WebHostBuilder()
|
|
||||||
.UseUrls(url)
|
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseIISIntegration()
|
|
||||||
.UseUrls(url)
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.Run(async context =>
|
|
||||||
{
|
|
||||||
if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration")
|
|
||||||
{
|
|
||||||
var json = JsonConvert.SerializeObject(_config);
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(json);
|
|
||||||
|
|
||||||
var base64 = Convert.ToBase64String(bytes);
|
|
||||||
|
|
||||||
var kvp = new FakeConsulGetResponse(base64);
|
|
||||||
|
|
||||||
await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp });
|
|
||||||
}
|
|
||||||
else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var reader = new StreamReader(context.Request.Body);
|
|
||||||
|
|
||||||
var json = reader.ReadToEnd();
|
|
||||||
|
|
||||||
_config = JsonConvert.DeserializeObject<FileConfiguration>(json);
|
|
||||||
|
|
||||||
var response = JsonConvert.SerializeObject(true);
|
|
||||||
|
|
||||||
await context.Response.WriteAsync(response);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.WriteLine(e);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
|
|
||||||
{
|
|
||||||
await context.Response.WriteJsonAsync(_consulServices);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_fakeConsulBuilder.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FakeConsulGetResponse
|
|
||||||
{
|
|
||||||
public FakeConsulGetResponse(string value)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CreateIndex => 100;
|
|
||||||
public int ModifyIndex => 200;
|
|
||||||
public int LockIndex => 200;
|
|
||||||
public string Key => "InternalConfiguration";
|
|
||||||
public int Flags => 0;
|
|
||||||
public string Value { get; private set; }
|
|
||||||
public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody)
|
|
||||||
{
|
|
||||||
_builder = new WebHostBuilder()
|
|
||||||
.UseUrls(url)
|
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseIISIntegration()
|
|
||||||
.UseUrls(url)
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.UsePathBase(basePath);
|
|
||||||
|
|
||||||
app.Run(async context =>
|
|
||||||
{
|
|
||||||
context.Response.StatusCode = statusCode;
|
|
||||||
await context.Response.WriteAsync(responseBody);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_builder.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_builder?.Dispose();
|
|
||||||
_steps.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
class FakeCache : IOcelotCache<FileConfiguration>
|
|
||||||
{
|
|
||||||
public void Add(string key, FileConfiguration value, TimeSpan ttl, string region)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddAndDelete(string key, FileConfiguration value, TimeSpan ttl, string region)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileConfiguration Get(string key, string region)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearRegion(string region)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -50,7 +50,6 @@
|
|||||||
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.500-preview2-1-003177" />
|
||||||
<PackageReference Include="Shouldly" Version="3.0.0" />
|
<PackageReference Include="Shouldly" Version="3.0.0" />
|
||||||
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
||||||
<PackageReference Include="Consul" Version="0.7.2.5" />
|
|
||||||
<PackageReference Include="xunit" Version="2.3.1" />
|
<PackageReference Include="xunit" Version="2.3.1" />
|
||||||
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
<PackageReference Include="Butterfly.Client.AspNetCore" Version="0.0.8" />
|
||||||
<PackageReference Include="Rafty" Version="0.4.4" />
|
<PackageReference Include="Rafty" Version="0.4.4" />
|
||||||
|
@ -2,12 +2,9 @@ namespace Ocelot.AcceptanceTests
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Consul;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Shouldly;
|
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -16,20 +13,13 @@ namespace Ocelot.AcceptanceTests
|
|||||||
public class ServiceDiscoveryTests : IDisposable
|
public class ServiceDiscoveryTests : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
private readonly List<ServiceEntry> _consulServices;
|
|
||||||
private readonly List<IServiceInstance> _eurekaInstances;
|
private readonly List<IServiceInstance> _eurekaInstances;
|
||||||
private int _counterOne;
|
|
||||||
private int _counterTwo;
|
|
||||||
private static readonly object SyncLock = new object();
|
|
||||||
private string _downstreamPath;
|
|
||||||
private string _receivedToken;
|
|
||||||
private readonly ServiceHandler _serviceHandler;
|
private readonly ServiceHandler _serviceHandler;
|
||||||
|
|
||||||
public ServiceDiscoveryTests()
|
public ServiceDiscoveryTests()
|
||||||
{
|
{
|
||||||
_serviceHandler = new ServiceHandler();
|
_serviceHandler = new ServiceHandler();
|
||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
_consulServices = new List<ServiceEntry>();
|
|
||||||
_eurekaInstances = new List<IServiceInstance>();
|
_eurekaInstances = new List<IServiceInstance>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,479 +70,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_use_consul_service_discovery_and_load_balance_request()
|
|
||||||
{
|
|
||||||
var consulPort = 8502;
|
|
||||||
var serviceName = "product";
|
|
||||||
var downstreamServiceOneUrl = "http://localhost:50881";
|
|
||||||
var downstreamServiceTwoUrl = "http://localhost:50882";
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = 50881,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
var serviceEntryTwo = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = 50882,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
UpstreamPathTemplate = "/",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
|
||||||
ServiceName = serviceName,
|
|
||||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
|
||||||
UseServiceDiscovery = true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
|
||||||
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
|
||||||
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
|
||||||
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50))
|
|
||||||
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
|
|
||||||
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_handle_request_to_consul_for_downstream_service_and_make_request()
|
|
||||||
{
|
|
||||||
const int consulPort = 8505;
|
|
||||||
const string serviceName = "web";
|
|
||||||
const string downstreamServiceOneUrl = "http://localhost:8080";
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = 8080,
|
|
||||||
ID = "web_90_0_2_224_8080",
|
|
||||||
Tags = new[] {"version-v1"}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/api/home",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
UpstreamPathTemplate = "/home",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
|
||||||
ServiceName = serviceName,
|
|
||||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
|
||||||
UseServiceDiscovery = true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura"))
|
|
||||||
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
|
||||||
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/home"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes()
|
|
||||||
{
|
|
||||||
const int consulPort = 8513;
|
|
||||||
const string serviceName = "web";
|
|
||||||
const int downstreamServicePort = 8087;
|
|
||||||
var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}";
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = downstreamServicePort,
|
|
||||||
ID = "web_90_0_2_224_8080",
|
|
||||||
Tags = new[] {"version-v1"}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
},
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
HttpHandlerOptions = new FileHttpHandlerOptions
|
|
||||||
{
|
|
||||||
AllowAutoRedirect = true,
|
|
||||||
UseCookieContainer = true,
|
|
||||||
UseTracing = false
|
|
||||||
},
|
|
||||||
QoSOptions = new FileQoSOptions
|
|
||||||
{
|
|
||||||
TimeoutValue = 100,
|
|
||||||
DurationOfBreak = 1000,
|
|
||||||
ExceptionsAllowedBeforeBreaking = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura"))
|
|
||||||
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
|
||||||
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/web/something"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_use_consul_service_discovery_and_load_balance_request_no_re_routes()
|
|
||||||
{
|
|
||||||
var consulPort = 8510;
|
|
||||||
var serviceName = "product";
|
|
||||||
var serviceOnePort = 50888;
|
|
||||||
var serviceTwoPort = 50889;
|
|
||||||
var downstreamServiceOneUrl = $"http://localhost:{serviceOnePort}";
|
|
||||||
var downstreamServiceTwoUrl = $"http://localhost:{serviceTwoPort}";
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = serviceOnePort,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
var serviceEntryTwo = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = serviceTwoPort,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
},
|
|
||||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
|
||||||
DownstreamScheme = "http"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
|
||||||
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
|
||||||
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
|
||||||
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes($"/{serviceName}/", 50))
|
|
||||||
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50))
|
|
||||||
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_use_token_to_make_request_to_consul()
|
|
||||||
{
|
|
||||||
var token = "abctoken";
|
|
||||||
var consulPort = 8515;
|
|
||||||
var serviceName = "web";
|
|
||||||
var downstreamServiceOneUrl = "http://localhost:8081";
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = 8081,
|
|
||||||
ID = "web_90_0_2_224_8080",
|
|
||||||
Tags = new[] { "version-v1" }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/api/home",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
UpstreamPathTemplate = "/home",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
|
||||||
ServiceName = serviceName,
|
|
||||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
|
||||||
UseServiceDiscovery = true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort,
|
|
||||||
Token = token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(_ => GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura"))
|
|
||||||
.And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
|
||||||
.And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
|
|
||||||
.And(_ => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(_ => _steps.GivenOcelotIsRunning())
|
|
||||||
.When(_ => _steps.WhenIGetUrlOnTheApiGateway("/home"))
|
|
||||||
.Then(_ => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(_ => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.And(_ => _receivedToken.ShouldBe(token))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_send_request_to_service_after_it_becomes_available_in_consul()
|
|
||||||
{
|
|
||||||
var consulPort = 8501;
|
|
||||||
var serviceName = "product";
|
|
||||||
var downstreamServiceOneUrl = "http://localhost:50879";
|
|
||||||
var downstreamServiceTwoUrl = "http://localhost:50880";
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = 50879,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
var serviceEntryTwo = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = 50880,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
UpstreamPathTemplate = "/",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
|
||||||
ServiceName = serviceName,
|
|
||||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
|
||||||
UseServiceDiscovery = true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200))
|
|
||||||
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200))
|
|
||||||
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
|
||||||
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
|
||||||
.And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10))
|
|
||||||
.And(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10))
|
|
||||||
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6))
|
|
||||||
.And(x => WhenIRemoveAService(serviceEntryTwo))
|
|
||||||
.And(x => GivenIResetCounters())
|
|
||||||
.And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10))
|
|
||||||
.And(x => ThenOnlyOneServiceHasBeenCalled())
|
|
||||||
.And(x => WhenIAddAServiceBackIn(serviceEntryTwo))
|
|
||||||
.And(x => GivenIResetCounters())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10))
|
|
||||||
.Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10))
|
|
||||||
.And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_handle_request_to_poll_consul_for_downstream_service_and_make_request()
|
|
||||||
{
|
|
||||||
const int consulPort = 8518;
|
|
||||||
const string serviceName = "web";
|
|
||||||
const int downstreamServicePort = 8082;
|
|
||||||
var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}";
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = downstreamServicePort,
|
|
||||||
ID = $"web_90_0_2_224_{downstreamServicePort}",
|
|
||||||
Tags = new[] {"version-v1"}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/api/home",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
UpstreamPathTemplate = "/home",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get", "Options" },
|
|
||||||
ServiceName = serviceName,
|
|
||||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" },
|
|
||||||
UseServiceDiscovery = true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort,
|
|
||||||
Type = "PollConsul",
|
|
||||||
PollingInterval = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura"))
|
|
||||||
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
|
||||||
.And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk("/home"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo)
|
|
||||||
{
|
|
||||||
_consulServices.Add(serviceEntryTwo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenOnlyOneServiceHasBeenCalled()
|
|
||||||
{
|
|
||||||
_counterOne.ShouldBe(10);
|
|
||||||
_counterTwo.ShouldBe(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WhenIRemoveAService(ServiceEntry serviceEntryTwo)
|
|
||||||
{
|
|
||||||
_consulServices.Remove(serviceEntryTwo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenIResetCounters()
|
|
||||||
{
|
|
||||||
_counterOne = 0;
|
|
||||||
_counterTwo = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top)
|
|
||||||
{
|
|
||||||
_counterOne.ShouldBeInRange(bottom, top);
|
|
||||||
_counterOne.ShouldBeInRange(bottom, top);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected)
|
|
||||||
{
|
|
||||||
var total = _counterOne + _counterTwo;
|
|
||||||
total.ShouldBe(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
|
|
||||||
{
|
|
||||||
foreach(var serviceEntry in serviceEntries)
|
|
||||||
{
|
|
||||||
_consulServices.Add(serviceEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenTheServicesAreRegisteredWithEureka(params IServiceInstance[] serviceInstances)
|
private void GivenTheServicesAreRegisteredWithEureka(params IServiceInstance[] serviceInstances)
|
||||||
{
|
{
|
||||||
foreach (var instance in serviceInstances)
|
foreach (var instance in serviceInstances)
|
||||||
@ -631,68 +148,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
|
|
||||||
{
|
|
||||||
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
|
|
||||||
{
|
|
||||||
if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
|
|
||||||
{
|
|
||||||
if (context.Request.Headers.TryGetValue("X-Consul-Token", out var values))
|
|
||||||
{
|
|
||||||
_receivedToken = values.First();
|
|
||||||
}
|
|
||||||
|
|
||||||
await context.Response.WriteJsonAsync(_consulServices);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenProductServiceOneIsRunning(string url, int statusCode)
|
|
||||||
{
|
|
||||||
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string response;
|
|
||||||
lock (SyncLock)
|
|
||||||
{
|
|
||||||
_counterOne++;
|
|
||||||
response = _counterOne.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Response.StatusCode = statusCode;
|
|
||||||
await context.Response.WriteAsync(response);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
await context.Response.WriteAsync(exception.StackTrace);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenProductServiceTwoIsRunning(string url, int statusCode)
|
|
||||||
{
|
|
||||||
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string response;
|
|
||||||
lock (SyncLock)
|
|
||||||
{
|
|
||||||
_counterTwo++;
|
|
||||||
response = _counterTwo.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Response.StatusCode = statusCode;
|
|
||||||
await context.Response.WriteAsync(response);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
await context.Response.WriteAsync(exception.StackTrace);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenEurekaProductServiceOneIsRunning(string url)
|
private void GivenEurekaProductServiceOneIsRunning(string url)
|
||||||
{
|
{
|
||||||
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
|
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
|
||||||
@ -709,25 +164,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_serviceHandler?.Dispose();
|
_serviceHandler?.Dispose();
|
||||||
|
@ -422,34 +422,6 @@
|
|||||||
header.First().ShouldBe(value);
|
header.First().ShouldBe(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GivenOcelotIsRunningUsingConsulToStoreConfig()
|
|
||||||
{
|
|
||||||
_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().AddStoreOcelotConfigurationInConsul();
|
|
||||||
})
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.UseOcelot().Wait();
|
|
||||||
});
|
|
||||||
|
|
||||||
_ocelotServer = new TestServer(_webHostBuilder);
|
|
||||||
|
|
||||||
_ocelotClient = _ocelotServer.CreateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
|
/// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -576,24 +548,6 @@
|
|||||||
_response = _ocelotClient.GetAsync(url).Result;
|
_response = _ocelotClient.GetAsync(url).Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk(string url)
|
|
||||||
{
|
|
||||||
var result = WaitFor(2000).Until(() => {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_response = _ocelotClient.GetAsync(url).Result;
|
|
||||||
_response.EnsureSuccessStatusCode();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch(Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
result.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value)
|
public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value)
|
||||||
{
|
{
|
||||||
var request = _ocelotServer.CreateRequest(url);
|
var request = _ocelotServer.CreateRequest(url);
|
||||||
|
@ -1,151 +0,0 @@
|
|||||||
namespace Ocelot.AcceptanceTests
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net;
|
|
||||||
using Consul;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Ocelot.Configuration.File;
|
|
||||||
using TestStack.BDDfy;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
public class TwoDownstreamServicesTests : IDisposable
|
|
||||||
{
|
|
||||||
private readonly Steps _steps;
|
|
||||||
private readonly List<ServiceEntry> _serviceEntries;
|
|
||||||
private string _downstreamPathOne;
|
|
||||||
private string _downstreamPathTwo;
|
|
||||||
private readonly ServiceHandler _serviceHandler;
|
|
||||||
|
|
||||||
public TwoDownstreamServicesTests()
|
|
||||||
{
|
|
||||||
_serviceHandler = new ServiceHandler();
|
|
||||||
_steps = new Steps();
|
|
||||||
_serviceEntries = new List<ServiceEntry>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_fix_issue_194()
|
|
||||||
{
|
|
||||||
var consulPort = 8503;
|
|
||||||
var downstreamServiceOneUrl = "http://localhost:8362";
|
|
||||||
var downstreamServiceTwoUrl = "http://localhost:8330";
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
|
|
||||||
var configuration = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/api/user/{user}",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 8362,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpstreamPathTemplate = "/api/user/{user}",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
|
||||||
},
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamPathTemplate = "/api/product/{product}",
|
|
||||||
DownstreamScheme = "http",
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = 8330,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpstreamPathTemplate = "/api/product/{product}",
|
|
||||||
UpstreamHttpMethod = new List<string> { "Get" },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration()
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider()
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, "/api/user/info", 200, "user"))
|
|
||||||
.And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, "/api/product/info", 200, "product"))
|
|
||||||
.And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl))
|
|
||||||
.And(x => _steps.GivenThereIsAConfiguration(configuration))
|
|
||||||
.And(x => _steps.GivenOcelotIsRunning())
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/user/info?id=1"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("user"))
|
|
||||||
.When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/product/info?id=1"))
|
|
||||||
.Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
|
|
||||||
.And(x => _steps.ThenTheResponseBodyShouldBe("product"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url)
|
|
||||||
{
|
|
||||||
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
|
|
||||||
{
|
|
||||||
if (context.Request.Path.Value == "/v1/health/service/product")
|
|
||||||
{
|
|
||||||
await context.Response.WriteJsonAsync(_serviceEntries);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenProductServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
|
|
||||||
{
|
|
||||||
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenProductServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody)
|
|
||||||
{
|
|
||||||
_serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, 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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_serviceHandler?.Dispose();
|
|
||||||
_steps.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,8 +6,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Consul;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Ocelot.Configuration.File;
|
using Ocelot.Configuration.File;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
@ -17,7 +15,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
{
|
{
|
||||||
private readonly List<string> _secondRecieved;
|
private readonly List<string> _secondRecieved;
|
||||||
private readonly List<string> _firstRecieved;
|
private readonly List<string> _firstRecieved;
|
||||||
private readonly List<ServiceEntry> _serviceEntries;
|
|
||||||
private readonly Steps _steps;
|
private readonly Steps _steps;
|
||||||
private readonly ServiceHandler _serviceHandler;
|
private readonly ServiceHandler _serviceHandler;
|
||||||
|
|
||||||
@ -27,7 +24,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
_steps = new Steps();
|
_steps = new Steps();
|
||||||
_firstRecieved = new List<string>();
|
_firstRecieved = new List<string>();
|
||||||
_secondRecieved = new List<string>();
|
_secondRecieved = new List<string>();
|
||||||
_serviceEntries = new List<ServiceEntry>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -109,77 +105,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_proxy_websocket_input_to_downstream_service_and_use_service_discovery_and_load_balancer()
|
|
||||||
{
|
|
||||||
var downstreamPort = 5007;
|
|
||||||
var downstreamHost = "localhost";
|
|
||||||
|
|
||||||
var secondDownstreamPort = 5008;
|
|
||||||
var secondDownstreamHost = "localhost";
|
|
||||||
|
|
||||||
var serviceName = "websockets";
|
|
||||||
var consulPort = 8509;
|
|
||||||
var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}";
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = downstreamHost,
|
|
||||||
Port = downstreamPort,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
var serviceEntryTwo = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = serviceName,
|
|
||||||
Address = secondDownstreamHost,
|
|
||||||
Port = secondDownstreamPort,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var config = new FileConfiguration
|
|
||||||
{
|
|
||||||
ReRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
UpstreamPathTemplate = "/",
|
|
||||||
DownstreamPathTemplate = "/ws",
|
|
||||||
DownstreamScheme = "ws",
|
|
||||||
LoadBalancerOptions = new FileLoadBalancerOptions { Type = "RoundRobin" },
|
|
||||||
ServiceName = serviceName,
|
|
||||||
UseServiceDiscovery = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
GlobalConfiguration = new FileGlobalConfiguration
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
|
||||||
{
|
|
||||||
Host = "localhost",
|
|
||||||
Port = consulPort,
|
|
||||||
Type = "consul"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(_ => _steps.GivenThereIsAConfiguration(config))
|
|
||||||
.And(_ => _steps.StartFakeOcelotWithWebSockets())
|
|
||||||
.And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName))
|
|
||||||
.And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
|
||||||
.And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws"))
|
|
||||||
.And(_ => StartSecondFakeDownstreamService($"http://{secondDownstreamHost}:{secondDownstreamPort}", "/ws"))
|
|
||||||
.When(_ => WhenIStartTheClients())
|
|
||||||
.Then(_ => ThenBothDownstreamServicesAreCalled())
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenBothDownstreamServicesAreCalled()
|
private void ThenBothDownstreamServicesAreCalled()
|
||||||
{
|
{
|
||||||
_firstRecieved.Count.ShouldBe(10);
|
_firstRecieved.Count.ShouldBe(10);
|
||||||
@ -195,25 +120,6 @@ namespace Ocelot.AcceptanceTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
|
|
||||||
{
|
|
||||||
foreach (var serviceEntry in serviceEntries)
|
|
||||||
{
|
|
||||||
_serviceEntries.Add(serviceEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
|
|
||||||
{
|
|
||||||
_serviceHandler.GivenThereIsAServiceRunningOn(url, async context =>
|
|
||||||
{
|
|
||||||
if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
|
|
||||||
{
|
|
||||||
await context.Response.WriteJsonAsync(_serviceEntries);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task WhenIStartTheClients()
|
private async Task WhenIStartTheClients()
|
||||||
{
|
{
|
||||||
var firstClient = StartClient("ws://localhost:5000/");
|
var firstClient = StartClient("ws://localhost:5000/");
|
||||||
|
@ -42,7 +42,6 @@
|
|||||||
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
<PackageReference Include="IdentityServer4" Version="2.2.0" />
|
||||||
<PackageReference Include="Shouldly" Version="3.0.0" />
|
<PackageReference Include="Shouldly" Version="3.0.0" />
|
||||||
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
<PackageReference Include="TestStack.BDDfy" Version="4.3.2" />
|
||||||
<PackageReference Include="Consul" Version="0.7.2.5" />
|
|
||||||
<PackageReference Include="Rafty" Version="0.4.4" />
|
<PackageReference Include="Rafty" Version="0.4.4" />
|
||||||
<PackageReference Include="Microsoft.Data.SQLite" Version="2.1.0" />
|
<PackageReference Include="Microsoft.Data.SQLite" Version="2.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -36,7 +36,6 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.1.1" />
|
||||||
<PackageReference Include="Consul" Version="0.7.2.5" />
|
|
||||||
<PackageReference Include="Polly" Version="6.0.1" />
|
<PackageReference Include="Polly" Version="6.0.1" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
@ -1,263 +0,0 @@
|
|||||||
namespace Ocelot.UnitTests.Configuration
|
|
||||||
{
|
|
||||||
using Xunit;
|
|
||||||
using TestStack.BDDfy;
|
|
||||||
using Shouldly;
|
|
||||||
using Ocelot.Configuration.Repository;
|
|
||||||
using Moq;
|
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.Configuration.File;
|
|
||||||
using Ocelot.Cache;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Ocelot.Responses;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ocelot.Configuration;
|
|
||||||
using Ocelot.Configuration.Builder;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
using Consul;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
public class ConsulFileConfigurationRepositoryTests
|
|
||||||
{
|
|
||||||
private ConsulFileConfigurationRepository _repo;
|
|
||||||
private Mock<IOcelotCache<FileConfiguration>> _cache;
|
|
||||||
private Mock<IInternalConfigurationRepository> _internalRepo;
|
|
||||||
private Mock<IConsulClientFactory> _factory;
|
|
||||||
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
|
||||||
private Mock<IConsulClient> _client;
|
|
||||||
private Mock<IKVEndpoint> _kvEndpoint;
|
|
||||||
private FileConfiguration _fileConfiguration;
|
|
||||||
private Response _setResult;
|
|
||||||
private Response<FileConfiguration> _getResult;
|
|
||||||
|
|
||||||
public ConsulFileConfigurationRepositoryTests()
|
|
||||||
{
|
|
||||||
_cache = new Mock<IOcelotCache<FileConfiguration>>();
|
|
||||||
_internalRepo = new Mock<IInternalConfigurationRepository>();
|
|
||||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
|
||||||
|
|
||||||
_factory = new Mock<IConsulClientFactory>();
|
|
||||||
_client = new Mock<IConsulClient>();
|
|
||||||
_kvEndpoint = new Mock<IKVEndpoint>();
|
|
||||||
|
|
||||||
_client
|
|
||||||
.Setup(x => x.KV)
|
|
||||||
.Returns(_kvEndpoint.Object);
|
|
||||||
|
|
||||||
_factory
|
|
||||||
.Setup(x => x.Get(It.IsAny<ConsulRegistryConfiguration>()))
|
|
||||||
.Returns(_client.Object);
|
|
||||||
|
|
||||||
_internalRepo
|
|
||||||
.Setup(x => x.Get())
|
|
||||||
.Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(new List<ReRoute>(), "", new ServiceProviderConfigurationBuilder().Build(), "", It.IsAny<LoadBalancerOptions>(), It.IsAny<string>(), It.IsAny<QoSOptions>(), It.IsAny<HttpHandlerOptions>())));
|
|
||||||
|
|
||||||
_repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_set_config()
|
|
||||||
{
|
|
||||||
var config = FakeFileConfiguration();
|
|
||||||
|
|
||||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
|
||||||
.And(_ => GivenWritingToConsulSucceeds())
|
|
||||||
.When(_ => WhenISetTheConfiguration())
|
|
||||||
.Then(_ => ThenTheConfigurationIsStoredAs(config))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_get_config()
|
|
||||||
{
|
|
||||||
var config = FakeFileConfiguration();
|
|
||||||
|
|
||||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
|
||||||
.And(_ => GivenFetchFromConsulSucceeds())
|
|
||||||
.When(_ => WhenIGetTheConfiguration())
|
|
||||||
.Then(_ => ThenTheConfigurationIs(config))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_get_null_config()
|
|
||||||
{
|
|
||||||
this.Given(_ => GivenFetchFromConsulReturnsNull())
|
|
||||||
.When(_ => WhenIGetTheConfiguration())
|
|
||||||
.Then(_ => ThenTheConfigurationIsNull())
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_get_config_from_cache()
|
|
||||||
{
|
|
||||||
var config = FakeFileConfiguration();
|
|
||||||
|
|
||||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
|
||||||
.And(_ => GivenFetchFromCacheSucceeds())
|
|
||||||
.When(_ => WhenIGetTheConfiguration())
|
|
||||||
.Then(_ => ThenTheConfigurationIs(config))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_set_config_key()
|
|
||||||
{
|
|
||||||
var config = FakeFileConfiguration();
|
|
||||||
|
|
||||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
|
||||||
.And(_ => GivenTheConfigKeyComesFromFileConfig("Tom"))
|
|
||||||
.And(_ => GivenFetchFromConsulSucceeds())
|
|
||||||
.When(_ => WhenIGetTheConfiguration())
|
|
||||||
.And(_ => ThenTheConfigKeyIs("Tom"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_set_default_config_key()
|
|
||||||
{
|
|
||||||
var config = FakeFileConfiguration();
|
|
||||||
|
|
||||||
this.Given(_ => GivenIHaveAConfiguration(config))
|
|
||||||
.And(_ => GivenFetchFromConsulSucceeds())
|
|
||||||
.When(_ => WhenIGetTheConfiguration())
|
|
||||||
.And(_ => ThenTheConfigKeyIs("InternalConfiguration"))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheConfigKeyIs(string expected)
|
|
||||||
{
|
|
||||||
_kvEndpoint
|
|
||||||
.Verify(x => x.Get(expected, It.IsAny<CancellationToken>()), Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenTheConfigKeyComesFromFileConfig(string key)
|
|
||||||
{
|
|
||||||
_internalRepo
|
|
||||||
.Setup(x => x.Get())
|
|
||||||
.Returns(new OkResponse<IInternalConfiguration>(new InternalConfiguration(new List<ReRoute>(), "",
|
|
||||||
new ServiceProviderConfigurationBuilder().WithConfigurationKey(key).Build(), "",
|
|
||||||
new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(),
|
|
||||||
new HttpHandlerOptionsBuilder().Build())));
|
|
||||||
|
|
||||||
_repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheConfigurationIsNull()
|
|
||||||
{
|
|
||||||
_getResult.Data.ShouldBeNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheConfigurationIs(FileConfiguration config)
|
|
||||||
{
|
|
||||||
var expected = JsonConvert.SerializeObject(config, Formatting.Indented);
|
|
||||||
var result = JsonConvert.SerializeObject(_getResult.Data, Formatting.Indented);
|
|
||||||
result.ShouldBe(expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task WhenIGetTheConfiguration()
|
|
||||||
{
|
|
||||||
_getResult = await _repo.Get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenWritingToConsulSucceeds()
|
|
||||||
{
|
|
||||||
var response = new WriteResult<bool>();
|
|
||||||
response.Response = true;
|
|
||||||
|
|
||||||
_kvEndpoint
|
|
||||||
.Setup(x => x.Put(It.IsAny<KVPair>(), It.IsAny<CancellationToken>())).ReturnsAsync(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenFetchFromCacheSucceeds()
|
|
||||||
{
|
|
||||||
_cache.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Returns(_fileConfiguration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenFetchFromConsulReturnsNull()
|
|
||||||
{
|
|
||||||
QueryResult<KVPair> result = new QueryResult<KVPair>();
|
|
||||||
|
|
||||||
_kvEndpoint
|
|
||||||
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
|
||||||
.ReturnsAsync(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenFetchFromConsulSucceeds()
|
|
||||||
{
|
|
||||||
var json = JsonConvert.SerializeObject(_fileConfiguration, Formatting.Indented);
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(json);
|
|
||||||
|
|
||||||
var kvp = new KVPair("OcelotConfiguration");
|
|
||||||
kvp.Value = bytes;
|
|
||||||
|
|
||||||
var query = new QueryResult<KVPair>();
|
|
||||||
query.Response = kvp;
|
|
||||||
|
|
||||||
_kvEndpoint
|
|
||||||
.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
|
||||||
.ReturnsAsync(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheConfigurationIsStoredAs(FileConfiguration config)
|
|
||||||
{
|
|
||||||
var json = JsonConvert.SerializeObject(config, Formatting.Indented);
|
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(json);
|
|
||||||
|
|
||||||
_kvEndpoint
|
|
||||||
.Verify(x => x.Put(It.Is<KVPair>(k => k.Value.SequenceEqual(bytes)), It.IsAny<CancellationToken>()), Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task WhenISetTheConfiguration()
|
|
||||||
{
|
|
||||||
_setResult = await _repo.Set(_fileConfiguration);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenIHaveAConfiguration(FileConfiguration config)
|
|
||||||
{
|
|
||||||
_fileConfiguration = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FileConfiguration FakeFileConfiguration()
|
|
||||||
{
|
|
||||||
var reRoutes = new List<FileReRoute>
|
|
||||||
{
|
|
||||||
new FileReRoute
|
|
||||||
{
|
|
||||||
DownstreamHostAndPorts = new List<FileHostAndPort>
|
|
||||||
{
|
|
||||||
new FileHostAndPort
|
|
||||||
{
|
|
||||||
Host = "123.12.12.12",
|
|
||||||
Port = 80,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DownstreamScheme = "https",
|
|
||||||
DownstreamPathTemplate = "/asdfs/test/{test}"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var globalConfiguration = new FileGlobalConfiguration
|
|
||||||
{
|
|
||||||
ServiceDiscoveryProvider = new FileServiceDiscoveryProvider
|
|
||||||
{
|
|
||||||
Port = 198,
|
|
||||||
Host = "blah"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return new FileConfiguration
|
|
||||||
{
|
|
||||||
GlobalConfiguration = globalConfiguration,
|
|
||||||
ReRoutes = reRoutes
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +1,23 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Hosting.Internal;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Ocelot.Cache;
|
|
||||||
using Ocelot.Configuration;
|
|
||||||
using Ocelot.Configuration.File;
|
|
||||||
using Ocelot.Configuration.Setter;
|
|
||||||
using Ocelot.DependencyInjection;
|
|
||||||
using Ocelot.Requester;
|
|
||||||
using Ocelot.UnitTests.Requester;
|
|
||||||
using Shouldly;
|
|
||||||
using IdentityServer4.AccessTokenValidation;
|
|
||||||
using TestStack.BDDfy;
|
|
||||||
using Xunit;
|
|
||||||
using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests;
|
|
||||||
using Ocelot.Middleware.Multiplexer;
|
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.DependencyInjection
|
namespace Ocelot.UnitTests.DependencyInjection
|
||||||
{
|
{
|
||||||
using Butterfly;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Internal;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Ocelot.Configuration.Setter;
|
||||||
|
using Ocelot.DependencyInjection;
|
||||||
|
using Ocelot.Requester;
|
||||||
|
using Ocelot.UnitTests.Requester;
|
||||||
|
using Shouldly;
|
||||||
|
using IdentityServer4.AccessTokenValidation;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests;
|
||||||
|
using Ocelot.Middleware.Multiplexer;
|
||||||
|
|
||||||
public class OcelotBuilderTests
|
public class OcelotBuilderTests
|
||||||
{
|
{
|
||||||
@ -80,15 +75,6 @@ namespace Ocelot.UnitTests.DependencyInjection
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_set_up_consul()
|
|
||||||
{
|
|
||||||
this.Given(x => WhenISetUpOcelotServices())
|
|
||||||
.When(x => WhenISetUpConsul())
|
|
||||||
.Then(x => ThenAnExceptionIsntThrown())
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_set_up_rafty()
|
public void should_set_up_rafty()
|
||||||
{
|
{
|
||||||
@ -271,18 +257,6 @@ namespace Ocelot.UnitTests.DependencyInjection
|
|||||||
first.ShouldBe(second);
|
first.ShouldBe(second);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WhenISetUpConsul()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_ocelotBuilder.AddStoreOcelotConfigurationInConsul();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_ex = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WhenISetUpRafty()
|
private void WhenISetUpRafty()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -22,7 +22,7 @@ namespace Ocelot.UnitTests.Infrastructure
|
|||||||
called = true;
|
called = true;
|
||||||
});
|
});
|
||||||
_bus.Publish(new object(), 1);
|
_bus.Publish(new object(), 1);
|
||||||
await Task.Delay(10);
|
await Task.Delay(100);
|
||||||
called.ShouldBeTrue();
|
called.ShouldBeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,297 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Consul;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Moq;
|
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.ServiceDiscovery.Configuration;
|
|
||||||
using Ocelot.ServiceDiscovery.Providers;
|
|
||||||
using Ocelot.Values;
|
|
||||||
using Xunit;
|
|
||||||
using TestStack.BDDfy;
|
|
||||||
using Shouldly;
|
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.ServiceDiscovery
|
|
||||||
{
|
|
||||||
public class ConsulServiceDiscoveryProviderTests : IDisposable
|
|
||||||
{
|
|
||||||
private IWebHost _fakeConsulBuilder;
|
|
||||||
private readonly List<ServiceEntry> _serviceEntries;
|
|
||||||
private ConsulServiceDiscoveryProvider _provider;
|
|
||||||
private readonly string _serviceName;
|
|
||||||
private readonly int _port;
|
|
||||||
private readonly string _consulHost;
|
|
||||||
private readonly string _fakeConsulServiceDiscoveryUrl;
|
|
||||||
private List<Service> _services;
|
|
||||||
private readonly Mock<IOcelotLoggerFactory> _factory;
|
|
||||||
private readonly Mock<IOcelotLogger> _logger;
|
|
||||||
private string _receivedToken;
|
|
||||||
private readonly IConsulClientFactory _clientFactory;
|
|
||||||
|
|
||||||
public ConsulServiceDiscoveryProviderTests()
|
|
||||||
{
|
|
||||||
_serviceName = "test";
|
|
||||||
_port = 8500;
|
|
||||||
_consulHost = "localhost";
|
|
||||||
_fakeConsulServiceDiscoveryUrl = $"http://{_consulHost}:{_port}";
|
|
||||||
_serviceEntries = new List<ServiceEntry>();
|
|
||||||
|
|
||||||
_factory = new Mock<IOcelotLoggerFactory>();
|
|
||||||
_clientFactory = new ConsulClientFactory();
|
|
||||||
_logger = new Mock<IOcelotLogger>();
|
|
||||||
_factory.Setup(x => x.CreateLogger<ConsulServiceDiscoveryProvider>()).Returns(_logger.Object);
|
|
||||||
|
|
||||||
var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName, null);
|
|
||||||
_provider = new ConsulServiceDiscoveryProvider(config, _factory.Object, _clientFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_return_service_from_consul()
|
|
||||||
{
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = _serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = 50881,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x =>GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName))
|
|
||||||
.And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
|
|
||||||
.When(x => WhenIGetTheServices())
|
|
||||||
.Then(x => ThenTheCountIs(1))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_use_token()
|
|
||||||
{
|
|
||||||
var token = "test token";
|
|
||||||
var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName, token);
|
|
||||||
_provider = new ConsulServiceDiscoveryProvider(config, _factory.Object, _clientFactory);
|
|
||||||
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = _serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = 50881,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName))
|
|
||||||
.And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne))
|
|
||||||
.When(_ => WhenIGetTheServices())
|
|
||||||
.Then(_ => ThenTheCountIs(1))
|
|
||||||
.And(_ => _receivedToken.ShouldBe(token))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_not_return_services_with_invalid_address()
|
|
||||||
{
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = _serviceName,
|
|
||||||
Address = "http://localhost",
|
|
||||||
Port = 50881,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var serviceEntryTwo = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = _serviceName,
|
|
||||||
Address = "http://localhost",
|
|
||||||
Port = 50888,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName))
|
|
||||||
.And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
|
||||||
.When(x => WhenIGetTheServices())
|
|
||||||
.Then(x => ThenTheCountIs(0))
|
|
||||||
.And(x => ThenTheLoggerHasBeenCalledCorrectlyForInvalidAddress())
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_not_return_services_with_empty_address()
|
|
||||||
{
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = _serviceName,
|
|
||||||
Address = "",
|
|
||||||
Port = 50881,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var serviceEntryTwo = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = _serviceName,
|
|
||||||
Address = null,
|
|
||||||
Port = 50888,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName))
|
|
||||||
.And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
|
||||||
.When(x => WhenIGetTheServices())
|
|
||||||
.Then(x => ThenTheCountIs(0))
|
|
||||||
.And(x => ThenTheLoggerHasBeenCalledCorrectlyForEmptyAddress())
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_not_return_services_with_invalid_port()
|
|
||||||
{
|
|
||||||
var serviceEntryOne = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = _serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = -1,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var serviceEntryTwo = new ServiceEntry()
|
|
||||||
{
|
|
||||||
Service = new AgentService()
|
|
||||||
{
|
|
||||||
Service = _serviceName,
|
|
||||||
Address = "localhost",
|
|
||||||
Port = 0,
|
|
||||||
ID = Guid.NewGuid().ToString(),
|
|
||||||
Tags = new string[0]
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName))
|
|
||||||
.And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo))
|
|
||||||
.When(x => WhenIGetTheServices())
|
|
||||||
.Then(x => ThenTheCountIs(0))
|
|
||||||
.And(x => ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts())
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidAddress()
|
|
||||||
{
|
|
||||||
_logger.Verify(
|
|
||||||
x => x.LogWarning(
|
|
||||||
"Unable to use service Address: http://localhost and Port: 50881 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
|
|
||||||
Times.Once);
|
|
||||||
|
|
||||||
_logger.Verify(
|
|
||||||
x => x.LogWarning(
|
|
||||||
"Unable to use service Address: http://localhost and Port: 50888 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
|
|
||||||
Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheLoggerHasBeenCalledCorrectlyForEmptyAddress()
|
|
||||||
{
|
|
||||||
_logger.Verify(
|
|
||||||
x => x.LogWarning(
|
|
||||||
"Unable to use service Address: and Port: 50881 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
|
|
||||||
Times.Once);
|
|
||||||
|
|
||||||
_logger.Verify(
|
|
||||||
x => x.LogWarning(
|
|
||||||
"Unable to use service Address: and Port: 50888 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
|
|
||||||
Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts()
|
|
||||||
{
|
|
||||||
_logger.Verify(
|
|
||||||
x => x.LogWarning(
|
|
||||||
"Unable to use service Address: localhost and Port: -1 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
|
|
||||||
Times.Once);
|
|
||||||
|
|
||||||
_logger.Verify(
|
|
||||||
x => x.LogWarning(
|
|
||||||
"Unable to use service Address: localhost and Port: 0 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"),
|
|
||||||
Times.Once);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheCountIs(int count)
|
|
||||||
{
|
|
||||||
_services.Count.ShouldBe(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WhenIGetTheServices()
|
|
||||||
{
|
|
||||||
_services = _provider.Get().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries)
|
|
||||||
{
|
|
||||||
foreach (var serviceEntry in serviceEntries)
|
|
||||||
{
|
|
||||||
_serviceEntries.Add(serviceEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName)
|
|
||||||
{
|
|
||||||
_fakeConsulBuilder = new WebHostBuilder()
|
|
||||||
.UseUrls(url)
|
|
||||||
.UseKestrel()
|
|
||||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
|
||||||
.UseIISIntegration()
|
|
||||||
.UseUrls(url)
|
|
||||||
.Configure(app =>
|
|
||||||
{
|
|
||||||
app.Run(async context =>
|
|
||||||
{
|
|
||||||
if (context.Request.Path.Value == $"/v1/health/service/{serviceName}")
|
|
||||||
{
|
|
||||||
if (context.Request.Headers.TryGetValue("X-Consul-Token", out var values))
|
|
||||||
{
|
|
||||||
_receivedToken = values.First();
|
|
||||||
}
|
|
||||||
|
|
||||||
await context.Response.WriteJsonAsync(_serviceEntries);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
_fakeConsulBuilder.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_fakeConsulBuilder?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
namespace Ocelot.UnitTests.ServiceDiscovery
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Moq;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.ServiceDiscovery.Providers;
|
|
||||||
using Ocelot.Values;
|
|
||||||
using Xunit;
|
|
||||||
using TestStack.BDDfy;
|
|
||||||
using Shouldly;
|
|
||||||
using static Ocelot.Infrastructure.Wait;
|
|
||||||
|
|
||||||
public class PollingConsulServiceDiscoveryProviderTests
|
|
||||||
{
|
|
||||||
private readonly int _delay;
|
|
||||||
private PollingConsulServiceDiscoveryProvider _provider;
|
|
||||||
private readonly List<Service> _services;
|
|
||||||
private readonly Mock<IOcelotLoggerFactory> _factory;
|
|
||||||
private readonly Mock<IOcelotLogger> _logger;
|
|
||||||
private readonly Mock<IServiceDiscoveryProvider> _consulServiceDiscoveryProvider;
|
|
||||||
private List<Service> _result;
|
|
||||||
|
|
||||||
public PollingConsulServiceDiscoveryProviderTests()
|
|
||||||
{
|
|
||||||
_services = new List<Service>();
|
|
||||||
_delay = 1;
|
|
||||||
_factory = new Mock<IOcelotLoggerFactory>();
|
|
||||||
_logger = new Mock<IOcelotLogger>();
|
|
||||||
_factory.Setup(x => x.CreateLogger<PollingConsulServiceDiscoveryProvider>()).Returns(_logger.Object);
|
|
||||||
_consulServiceDiscoveryProvider = new Mock<IServiceDiscoveryProvider>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_return_service_from_consul()
|
|
||||||
{
|
|
||||||
var service = new Service("", new ServiceHostAndPort("", 0), "", "", new List<string>());
|
|
||||||
|
|
||||||
this.Given(x => GivenConsulReturns(service))
|
|
||||||
.When(x => WhenIGetTheServices(1))
|
|
||||||
.Then(x => ThenTheCountIs(1))
|
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GivenConsulReturns(Service service)
|
|
||||||
{
|
|
||||||
_services.Add(service);
|
|
||||||
_consulServiceDiscoveryProvider.Setup(x => x.Get()).ReturnsAsync(_services);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThenTheCountIs(int count)
|
|
||||||
{
|
|
||||||
_result.Count.ShouldBe(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WhenIGetTheServices(int expected)
|
|
||||||
{
|
|
||||||
_provider = new PollingConsulServiceDiscoveryProvider(_delay, "", _factory.Object, _consulServiceDiscoveryProvider.Object);
|
|
||||||
|
|
||||||
var result = WaitFor(3000).Until(() => {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_result = _provider.Get().GetAwaiter().GetResult();
|
|
||||||
if(_result.Count == expected)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch(Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
result.ShouldBeTrue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +1,12 @@
|
|||||||
using Ocelot.ServiceDiscovery.Configuration;
|
namespace Ocelot.UnitTests.ServiceDiscovery
|
||||||
using Ocelot.ServiceDiscovery.Providers;
|
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.ServiceDiscovery
|
|
||||||
{
|
{
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using Consul;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Moq;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.ServiceDiscovery;
|
|
||||||
using Ocelot.Values;
|
using Ocelot.Values;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using TestStack.BDDfy;
|
using TestStack.BDDfy;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
|
using Ocelot.ServiceDiscovery.Configuration;
|
||||||
|
using Ocelot.ServiceDiscovery.Providers;
|
||||||
|
|
||||||
public class ServiceFabricServiceDiscoveryProviderTests
|
public class ServiceFabricServiceDiscoveryProviderTests
|
||||||
{
|
{
|
||||||
|
@ -1,39 +1,41 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Moq;
|
|
||||||
using Ocelot.Configuration;
|
|
||||||
using Ocelot.Configuration.Builder;
|
|
||||||
using Ocelot.Infrastructure.Consul;
|
|
||||||
using Ocelot.Logging;
|
|
||||||
using Ocelot.ServiceDiscovery;
|
|
||||||
using Ocelot.ServiceDiscovery.Providers;
|
|
||||||
using Shouldly;
|
|
||||||
using TestStack.BDDfy;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Ocelot.UnitTests.ServiceDiscovery
|
namespace Ocelot.UnitTests.ServiceDiscovery
|
||||||
{
|
{
|
||||||
using Pivotal.Discovery.Client;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Steeltoe.Common.Discovery;
|
using Steeltoe.Common.Discovery;
|
||||||
|
using Values;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Moq;
|
||||||
|
using Ocelot.Configuration;
|
||||||
|
using Ocelot.Configuration.Builder;
|
||||||
|
using Ocelot.Logging;
|
||||||
|
using Ocelot.ServiceDiscovery;
|
||||||
|
using Ocelot.ServiceDiscovery.Providers;
|
||||||
|
using Shouldly;
|
||||||
|
using TestStack.BDDfy;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
public class ServiceProviderFactoryTests
|
public class ServiceProviderFactoryTests
|
||||||
{
|
{
|
||||||
private ServiceProviderConfiguration _serviceConfig;
|
private ServiceProviderConfiguration _serviceConfig;
|
||||||
private IServiceDiscoveryProvider _result;
|
private IServiceDiscoveryProvider _result;
|
||||||
private readonly ServiceDiscoveryProviderFactory _factory;
|
private ServiceDiscoveryProviderFactory _factory;
|
||||||
private DownstreamReRoute _reRoute;
|
private DownstreamReRoute _reRoute;
|
||||||
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
private Mock<IOcelotLoggerFactory> _loggerFactory;
|
||||||
private Mock<IDiscoveryClient> _discoveryClient;
|
private Mock<IDiscoveryClient> _discoveryClient;
|
||||||
private Mock<IOcelotLogger> _logger;
|
private Mock<IOcelotLogger> _logger;
|
||||||
|
private IServiceProvider _provider;
|
||||||
|
private IServiceCollection _collection;
|
||||||
|
|
||||||
public ServiceProviderFactoryTests()
|
public ServiceProviderFactoryTests()
|
||||||
{
|
{
|
||||||
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
_loggerFactory = new Mock<IOcelotLoggerFactory>();
|
||||||
_logger = new Mock<IOcelotLogger>();
|
_logger = new Mock<IOcelotLogger>();
|
||||||
_loggerFactory.Setup(x => x.CreateLogger<PollingConsulServiceDiscoveryProvider>()).Returns(_logger.Object);
|
|
||||||
_discoveryClient = new Mock<IDiscoveryClient>();
|
_discoveryClient = new Mock<IDiscoveryClient>();
|
||||||
var consulClient = new Mock<IConsulClientFactory>();
|
_collection = new ServiceCollection();
|
||||||
_factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, consulClient.Object, _discoveryClient.Object);
|
_provider = _collection.BuildServiceProvider();
|
||||||
|
_factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _discoveryClient.Object, _provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -72,7 +74,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void should_return_consul_service_provider()
|
public void should_call_delegate()
|
||||||
{
|
{
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
var reRoute = new DownstreamReRouteBuilder()
|
||||||
.WithServiceName("product")
|
.WithServiceName("product")
|
||||||
@ -83,27 +85,9 @@ namespace Ocelot.UnitTests.ServiceDiscovery
|
|||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
||||||
|
.And(x => GivenAFakeDelegate())
|
||||||
.When(x => x.WhenIGetTheServiceProvider())
|
.When(x => x.WhenIGetTheServiceProvider())
|
||||||
.Then(x => x.ThenTheServiceProviderIs<ConsulServiceDiscoveryProvider>())
|
.Then(x => x.ThenTheDelegateIsCalled())
|
||||||
.BDDfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void should_return_polling_consul_service_provider()
|
|
||||||
{
|
|
||||||
var reRoute = new DownstreamReRouteBuilder()
|
|
||||||
.WithServiceName("product")
|
|
||||||
.WithUseServiceDiscovery(true)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
var serviceConfig = new ServiceProviderConfigurationBuilder()
|
|
||||||
.WithType("PollConsul")
|
|
||||||
.WithPollingInterval(100000)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute))
|
|
||||||
.When(x => x.WhenIGetTheServiceProvider())
|
|
||||||
.Then(x => x.ThenTheServiceProviderIs<PollingConsulServiceDiscoveryProvider>())
|
|
||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +127,27 @@ namespace Ocelot.UnitTests.ServiceDiscovery
|
|||||||
.BDDfy();
|
.BDDfy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GivenAFakeDelegate()
|
||||||
|
{
|
||||||
|
ServiceDiscoveryFinderDelegate fake = (provider, config, name) => new Fake();
|
||||||
|
_collection.AddSingleton(fake);
|
||||||
|
_provider = _collection.BuildServiceProvider();
|
||||||
|
_factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _discoveryClient.Object, _provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Fake : IServiceDiscoveryProvider
|
||||||
|
{
|
||||||
|
public Task<List<Service>> Get()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThenTheDelegateIsCalled()
|
||||||
|
{
|
||||||
|
_result.GetType().Name.ShouldBe("Fake");
|
||||||
|
}
|
||||||
|
|
||||||
private void ThenTheFollowingServicesAreReturned(List<DownstreamHostAndPort> downstreamAddresses)
|
private void ThenTheFollowingServicesAreReturned(List<DownstreamHostAndPort> downstreamAddresses)
|
||||||
{
|
{
|
||||||
var result = (ConfigurationServiceProvider)_result;
|
var result = (ConfigurationServiceProvider)_result;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user