Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1634 #1487 #1329 #1304 #1294 #793 Consul polling of services: enhancements and fix errors #1670

Merged
merged 26 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e36554c
fixing some issues in poll consul:
Jun 20, 2023
c3ea2db
line endings
Jun 20, 2023
6984d1d
adding some test cases
Jun 20, 2023
76a1ded
Using a lock instead of SemaphoreSlim
Jun 26, 2023
410a2ef
Improve code readability
raman-m Jun 27, 2023
ed9a49a
CA2211: Non-constant fields should not be visible
raman-m Jun 27, 2023
d8c4e68
Use IOcelotLogger to remove warnings & messages of static code analys…
raman-m Jun 27, 2023
0018a46
Fix errors with unit tests discovery. Remove legacy life hacks of dis…
raman-m Jun 27, 2023
bfa7d64
Update unit tests
raman-m Jun 27, 2023
8028783
Also refactoring the kubernetes provider factory (like consul and eur…
ggnaegi Jun 27, 2023
69651c2
shorten references...
ggnaegi Jun 27, 2023
e05bd92
const before...
ggnaegi Jun 27, 2023
0959ef8
Some minor fixes, using Equals Ordinal ignore case and a string const…
ggnaegi Jun 29, 2023
944ffd7
waiting a bit longer then?
ggnaegi Sep 16, 2023
9f5cbaf
@RaynaldM code review
raman-m Sep 18, 2023
29dbba7
renaming PollKubernetes to PollKube
ggnaegi Sep 18, 2023
b81d33e
... odd...
ggnaegi Sep 18, 2023
ef89925
... very odd, we have an issue with configuration update duration...
ggnaegi Sep 18, 2023
d895571
IDE0002: Name can be simplified
raman-m Sep 19, 2023
2161db0
All tests passing locally, hopefully it works online
ggnaegi Sep 19, 2023
f43d120
merge
ggnaegi Sep 26, 2023
24f2091
just a bit of cleanup
ggnaegi Sep 26, 2023
d3063ce
Merge branch 'develop' into bug/polling-consul
raman-m Sep 28, 2023
08ac86e
Merge branch 'develop' into bug/polling-consul
raman-m Sep 29, 2023
77bbaf0
Some missing braces and commas
ggnaegi Sep 29, 2023
1111071
Update servicediscovery.rst: Review and update "Consul" section
raman-m Sep 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 55 additions & 63 deletions src/Ocelot.Provider.Consul/Consul.cs
Original file line number Diff line number Diff line change
@@ -1,86 +1,78 @@
using Consul;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Infrastructure.Extensions;
using Ocelot.Logging;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;

namespace Ocelot.Provider.Consul
{
public class Consul : IServiceDiscoveryProvider
{
private readonly ConsulRegistryConfiguration _config;
private readonly IOcelotLogger _logger;
private readonly IConsulClient _consul;
private const string VersionPrefix = "version-";
namespace Ocelot.Provider.Consul;

public Consul(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory)
{
_config = config;
_logger = factory.CreateLogger<Consul>();
_consul = clientFactory.Get(_config);
}
public class Consul : IServiceDiscoveryProvider
{
private const string VersionPrefix = "version-";
private readonly ConsulRegistryConfiguration _config;
private readonly IConsulClient _consul;
private readonly IOcelotLogger _logger;

public async Task<List<Service>> Get()
{
var consulAddress = (_consul as ConsulClient)?.Config.Address;
_logger.LogDebug($"Querying Consul {consulAddress} about a service: {_config.KeyOfServiceInConsul}");
public Consul(ConsulRegistryConfiguration config, IOcelotLoggerFactory factory, IConsulClientFactory clientFactory)
{
_logger = factory.CreateLogger<Consul>();
_config = config;
_consul = clientFactory.Get(_config);
}

var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true);
public async Task<List<Service>> Get()
{
var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true);

var services = new List<Service>();
var services = new List<Service>();

foreach (var serviceEntry in queryResult.Response)
foreach (var serviceEntry in queryResult.Response)
{
if (IsValid(serviceEntry))
{
var address = serviceEntry.Service.Address;
var port = serviceEntry.Service.Port;

if (IsValid(serviceEntry))
var nodes = await _consul.Catalog.Nodes();
if (nodes.Response == null)
{
var nodes = await _consul.Catalog.Nodes();
if (nodes.Response == null)
{
services.Add(BuildService(serviceEntry, null));
}
else
{
var serviceNode = nodes.Response.FirstOrDefault(n => n.Address == address);
services.Add(BuildService(serviceEntry, serviceNode));
}

_logger.LogDebug($"Consul answer: Address: {address}, Port: {port}");
services.Add(BuildService(serviceEntry, null));
}
else
{
_logger.LogWarning($"Unable to use service Address: {address} and Port: {port} as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0");
var serviceNode = nodes.Response.FirstOrDefault(n => n.Address == serviceEntry.Service.Address);
services.Add(BuildService(serviceEntry, serviceNode));
}
}

return services.ToList();
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");
}
}

private static Service BuildService(ServiceEntry serviceEntry, Node serviceNode)
{
return new Service(
serviceEntry.Service.Service,
new ServiceHostAndPort(serviceNode == null ? serviceEntry.Service.Address : serviceNode.Name, serviceEntry.Service.Port),
serviceEntry.Service.ID,
GetVersionFromStrings(serviceEntry.Service.Tags),
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
}
return services.ToList();
}

private static 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;
}
private static Service BuildService(ServiceEntry serviceEntry, Node serviceNode)
{
return new Service(
serviceEntry.Service.Service,
new ServiceHostAndPort(serviceNode == null ? serviceEntry.Service.Address : serviceNode.Name,
serviceEntry.Service.Port),
serviceEntry.Service.ID,
GetVersionFromStrings(serviceEntry.Service.Tags),
serviceEntry.Service.Tags ?? Enumerable.Empty<string>());
}

return true;
}
private static bool IsValid(ServiceEntry serviceEntry)
{
return !string.IsNullOrEmpty(serviceEntry.Service.Address)
&& !serviceEntry.Service.Address.Contains("http://")
&& !serviceEntry.Service.Address.Contains("https://")
&& serviceEntry.Service.Port > 0;
}

private static string GetVersionFromStrings(IEnumerable<string> strings)
=> strings?
.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
.TrimStart(VersionPrefix);
private static string GetVersionFromStrings(IEnumerable<string> strings)
{
return strings
?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal))
.TrimStart(VersionPrefix);
}
}
20 changes: 7 additions & 13 deletions src/Ocelot.Provider.Consul/ConsulClientFactory.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
using Consul;
namespace Ocelot.Provider.Consul;

namespace Ocelot.Provider.Consul
public class ConsulClientFactory : IConsulClientFactory
{
public class ConsulClientFactory : IConsulClientFactory
public IConsulClient Get(ConsulRegistryConfiguration config)
{
public IConsulClient Get(ConsulRegistryConfiguration config)
return new ConsulClient(c =>
{
return new ConsulClient(c =>
{
c.Address = new Uri($"{config.Scheme}://{config.Host}:{config.Port}");
c.Address = new Uri($"{config.Scheme}://{config.Host}:{config.Port}");

if (!string.IsNullOrEmpty(config?.Token))
{
c.Token = config.Token;
}
});
}
if (!string.IsNullOrEmpty(config?.Token)) c.Token = config.Token;
});
}
}
109 changes: 52 additions & 57 deletions src/Ocelot.Provider.Consul/ConsulFileConfigurationRepository.cs
Original file line number Diff line number Diff line change
@@ -1,86 +1,81 @@
using Consul;
using System.Text;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Ocelot.Cache;
using Ocelot.Configuration.File;
using Ocelot.Configuration.Repository;
using Ocelot.Logging;
using Ocelot.Responses;
using System.Text;

namespace Ocelot.Provider.Consul
namespace Ocelot.Provider.Consul;

public class ConsulFileConfigurationRepository : IFileConfigurationRepository
{
public class ConsulFileConfigurationRepository : IFileConfigurationRepository
private readonly IOcelotCache<FileConfiguration> _cache;
private readonly string _configurationKey;
private readonly IConsulClient _consul;
private readonly IOcelotLogger _logger;

public ConsulFileConfigurationRepository(
IOptions<FileConfiguration> fileConfiguration,
IOcelotCache<FileConfiguration> cache,
IConsulClientFactory factory,
IOcelotLoggerFactory loggerFactory)
{
private readonly IConsulClient _consul;
private readonly string _configurationKey;
private readonly Cache.IOcelotCache<FileConfiguration> _cache;
private readonly IOcelotLogger _logger;

public ConsulFileConfigurationRepository(
IOptions<FileConfiguration> fileConfiguration,
Cache.IOcelotCache<FileConfiguration> cache,
IConsulClientFactory factory,
IOcelotLoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
_cache = cache;
_logger = loggerFactory.CreateLogger<ConsulFileConfigurationRepository>();
_cache = cache;

var serviceDiscoveryProvider = fileConfiguration.Value.GlobalConfiguration.ServiceDiscoveryProvider;
_configurationKey = string.IsNullOrWhiteSpace(serviceDiscoveryProvider.ConfigurationKey) ? "InternalConfiguration" :
serviceDiscoveryProvider.ConfigurationKey;
var serviceDiscoveryProvider = fileConfiguration.Value.GlobalConfiguration.ServiceDiscoveryProvider;
_configurationKey = string.IsNullOrWhiteSpace(serviceDiscoveryProvider.ConfigurationKey)
? "InternalConfiguration"
: serviceDiscoveryProvider.ConfigurationKey;

var config = new ConsulRegistryConfiguration(serviceDiscoveryProvider.Scheme, serviceDiscoveryProvider.Host,
serviceDiscoveryProvider.Port, _configurationKey, serviceDiscoveryProvider.Token);

_consul = factory.Get(config);
}
var config = new ConsulRegistryConfiguration(serviceDiscoveryProvider.Scheme, serviceDiscoveryProvider.Host,
serviceDiscoveryProvider.Port, _configurationKey, serviceDiscoveryProvider.Token);

public async Task<Response<FileConfiguration>> Get()
{
var config = _cache.Get(_configurationKey, _configurationKey);
_consul = factory.Get(config);
}

if (config != null)
{
return new OkResponse<FileConfiguration>(config);
}
public async Task<Response<FileConfiguration>> Get()
{
var config = _cache.Get(_configurationKey, _configurationKey);

var queryResult = await _consul.KV.Get(_configurationKey);
if (config != null) return new OkResponse<FileConfiguration>(config);

if (queryResult.Response == null)
{
return new OkResponse<FileConfiguration>(null);
}
var queryResult = await _consul.KV.Get(_configurationKey);

var bytes = queryResult.Response.Value;
if (queryResult.Response == null) return new OkResponse<FileConfiguration>(null);

var json = Encoding.UTF8.GetString(bytes);
var bytes = queryResult.Response.Value;

var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);
var json = Encoding.UTF8.GetString(bytes);

return new OkResponse<FileConfiguration>(consulConfig);
}
var consulConfig = JsonConvert.DeserializeObject<FileConfiguration>(json);

public async Task<Response> Set(FileConfiguration ocelotConfiguration)
{
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);
return new OkResponse<FileConfiguration>(consulConfig);
}

var bytes = Encoding.UTF8.GetBytes(json);
public async Task<Response> Set(FileConfiguration ocelotConfiguration)
{
var json = JsonConvert.SerializeObject(ocelotConfiguration, Formatting.Indented);

var kvPair = new KVPair(_configurationKey)
{
Value = bytes,
};
var bytes = Encoding.UTF8.GetBytes(json);

var result = await _consul.KV.Put(kvPair);
var kvPair = new KVPair(_configurationKey)
{
Value = bytes
};

if (result.Response)
{
_cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);
var result = await _consul.KV.Put(kvPair);

return new OkResponse();
}
if (result.Response)
{
_cache.AddAndDelete(_configurationKey, ocelotConfiguration, TimeSpan.FromSeconds(3), _configurationKey);

return new ErrorResponse(new UnableToSetConfigInConsulError($"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
return new OkResponse();
}

return new ErrorResponse(new UnableToSetConfigInConsulError(
$"Unable to set FileConfiguration in consul, response status code from consul was {result.StatusCode}"));
}
}
Loading