@@ -0,0 +1,38 @@ | |||
// Copyright (c) .NET Core Community. All rights reserved. | |||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||
namespace DotNetCore.CAP.Dashboard.NodeDiscovery | |||
{ | |||
public class DiscoveryOptions | |||
{ | |||
public const string DefaultDiscoveryServerHost = "localhost"; | |||
public const int DefaultDiscoveryServerPort = 8500; | |||
public const string DefaultCurrentNodeHostName = "localhost"; | |||
public const int DefaultCurrentNodePort = 5000; | |||
public const string DefaultMatchPath = "/cap"; | |||
public DiscoveryOptions() | |||
{ | |||
DiscoveryServerHostName = DefaultDiscoveryServerHost; | |||
DiscoveryServerPort = DefaultDiscoveryServerPort; | |||
CurrentNodeHostName = DefaultCurrentNodeHostName; | |||
CurrentNodePort = DefaultCurrentNodePort; | |||
MatchPath = DefaultMatchPath; | |||
} | |||
public string DiscoveryServerHostName { get; set; } | |||
public int DiscoveryServerPort { get; set; } | |||
public string CurrentNodeHostName { get; set; } | |||
public int CurrentNodePort { get; set; } | |||
public string NodeId { get; set; } | |||
public string NodeName { get; set; } | |||
public string MatchPath { get; set; } | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
// Copyright (c) .NET Core Community. All rights reserved. | |||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||
using System; | |||
using DotNetCore.CAP; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace DotNetCore.CAP.Dashboard.NodeDiscovery | |||
{ | |||
internal sealed class DiscoveryOptionsExtension : ICapOptionsExtension | |||
{ | |||
private readonly Action<DiscoveryOptions> _options; | |||
public DiscoveryOptionsExtension(Action<DiscoveryOptions> option) | |||
{ | |||
_options = option; | |||
} | |||
public void AddServices(IServiceCollection services) | |||
{ | |||
var discoveryOptions = new DiscoveryOptions(); | |||
_options?.Invoke(discoveryOptions); | |||
services.AddSingleton(discoveryOptions); | |||
services.AddSingleton<IDiscoveryProviderFactory, DiscoveryProviderFactory>(); | |||
services.AddSingleton<IProcessingServer, ConsulProcessingNodeServer>(); | |||
services.AddSingleton(x => | |||
{ | |||
var configOptions = x.GetService<DiscoveryOptions>(); | |||
var factory = x.GetService<IDiscoveryProviderFactory>(); | |||
return factory.Create(configOptions); | |||
}); | |||
} | |||
} | |||
public static class CapDiscoveryOptionsExtensions | |||
{ | |||
public static CapOptions UseDiscovery(this CapOptions capOptions) | |||
{ | |||
return capOptions.UseDiscovery(opt => { }); | |||
} | |||
public static CapOptions UseDiscovery(this CapOptions capOptions, Action<DiscoveryOptions> options) | |||
{ | |||
if (options == null) | |||
{ | |||
throw new ArgumentNullException(nameof(options)); | |||
} | |||
capOptions.RegisterExtension(new DiscoveryOptionsExtension(options)); | |||
return capOptions; | |||
} | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
// Copyright (c) .NET Core Community. All rights reserved. | |||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||
using System; | |||
using Microsoft.Extensions.Logging; | |||
namespace DotNetCore.CAP.Dashboard.NodeDiscovery | |||
{ | |||
internal class DiscoveryProviderFactory : IDiscoveryProviderFactory | |||
{ | |||
private readonly ILoggerFactory _loggerFactory; | |||
public DiscoveryProviderFactory(ILoggerFactory loggerFactory) | |||
{ | |||
_loggerFactory = loggerFactory; | |||
} | |||
public INodeDiscoveryProvider Create(DiscoveryOptions options) | |||
{ | |||
if (options == null) | |||
{ | |||
throw new ArgumentNullException(nameof(options)); | |||
} | |||
return new ConsulNodeDiscoveryProvider(_loggerFactory, options); | |||
} | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
// Copyright (c) .NET Core Community. All rights reserved. | |||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||
namespace DotNetCore.CAP.Dashboard.NodeDiscovery | |||
{ | |||
internal interface IDiscoveryProviderFactory | |||
{ | |||
INodeDiscoveryProvider Create(DiscoveryOptions options); | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
// Copyright (c) .NET Core Community. All rights reserved. | |||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using Consul; | |||
using Microsoft.Extensions.Logging; | |||
namespace DotNetCore.CAP.Dashboard.NodeDiscovery | |||
{ | |||
public class ConsulNodeDiscoveryProvider : INodeDiscoveryProvider, IDisposable | |||
{ | |||
private readonly ILogger<ConsulNodeDiscoveryProvider> _logger; | |||
private readonly DiscoveryOptions _options; | |||
private ConsulClient _consul; | |||
public ConsulNodeDiscoveryProvider(ILoggerFactory logger, DiscoveryOptions options) | |||
{ | |||
_logger = logger.CreateLogger<ConsulNodeDiscoveryProvider>(); | |||
_options = options; | |||
InitClient(); | |||
} | |||
public void Dispose() | |||
{ | |||
_consul.Dispose(); | |||
} | |||
public async Task<IList<Node>> GetNodes() | |||
{ | |||
try | |||
{ | |||
var nodes = new List<Node>(); | |||
var services = await _consul.Catalog.Services(); | |||
foreach (var service in services.Response) | |||
{ | |||
var serviceInfo = await _consul.Catalog.Service(service.Key); | |||
var node = serviceInfo.Response.SkipWhile(x => !x.ServiceTags.Contains("CAP")) | |||
.Select(info => new Node | |||
{ | |||
Id = info.ServiceID, | |||
Name = info.ServiceName, | |||
Address = info.ServiceAddress, | |||
Port = info.ServicePort, | |||
Tags = string.Join(", ", info.ServiceTags) | |||
}).ToList(); | |||
nodes.AddRange(node); | |||
} | |||
CapCache.Global.AddOrUpdate("cap.nodes.count", nodes.Count, TimeSpan.FromSeconds(60), true); | |||
return nodes; | |||
} | |||
catch (Exception ex) | |||
{ | |||
CapCache.Global.AddOrUpdate("cap.nodes.count", 0, TimeSpan.FromSeconds(20)); | |||
_logger.LogError( | |||
$"Get consul nodes raised an exception. Exception:{ex.Message},{ex.InnerException.Message}"); | |||
return null; | |||
} | |||
} | |||
public Task RegisterNode() | |||
{ | |||
try | |||
{ | |||
return _consul.Agent.ServiceRegister(new AgentServiceRegistration | |||
{ | |||
ID = _options.NodeId, | |||
Name = _options.NodeName, | |||
Address = _options.CurrentNodeHostName, | |||
Port = _options.CurrentNodePort, | |||
Tags = new[] {"CAP", "Client", "Dashboard"}, | |||
Check = new AgentServiceCheck | |||
{ | |||
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(30), | |||
Interval = TimeSpan.FromSeconds(10), | |||
Status = HealthStatus.Passing, | |||
HTTP = | |||
$"http://{_options.CurrentNodeHostName}:{_options.CurrentNodePort}{_options.MatchPath}/health" | |||
} | |||
}); | |||
} | |||
catch (Exception ex) | |||
{ | |||
_logger.LogError( | |||
$"Get consul nodes raised an exception. Exception:{ex.Message},{ex.InnerException.Message}"); | |||
return null; | |||
} | |||
} | |||
private void InitClient() | |||
{ | |||
_consul = new ConsulClient(config => | |||
{ | |||
config.WaitTime = TimeSpan.FromSeconds(5); | |||
config.Address = new Uri($"http://{_options.DiscoveryServerHostName}:{_options.DiscoveryServerPort}"); | |||
}); | |||
} | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
// Copyright (c) .NET Core Community. All rights reserved. | |||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||
using System.Collections.Generic; | |||
using System.Threading.Tasks; | |||
namespace DotNetCore.CAP.Dashboard.NodeDiscovery | |||
{ | |||
public interface INodeDiscoveryProvider | |||
{ | |||
Task<IList<Node>> GetNodes(); | |||
Task RegisterNode(); | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
// Copyright (c) .NET Core Community. All rights reserved. | |||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||
namespace DotNetCore.CAP.Dashboard.NodeDiscovery | |||
{ | |||
internal class ConsulProcessingNodeServer : IProcessingServer | |||
{ | |||
private readonly DiscoveryOptions _dashboardOptions; | |||
private readonly IDiscoveryProviderFactory _discoveryProviderFactory; | |||
public ConsulProcessingNodeServer( | |||
DiscoveryOptions dashboardOptions, | |||
IDiscoveryProviderFactory discoveryProviderFactory) | |||
{ | |||
_dashboardOptions = dashboardOptions; | |||
_discoveryProviderFactory = discoveryProviderFactory; | |||
} | |||
public void Start() | |||
{ | |||
var discoveryProvider = _discoveryProviderFactory.Create(_dashboardOptions); | |||
discoveryProvider.RegisterNode(); | |||
} | |||
public void Pulse() | |||
{ | |||
//ignore | |||
} | |||
public void Dispose() | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,18 @@ | |||
// Copyright (c) .NET Core Community. All rights reserved. | |||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||
namespace DotNetCore.CAP.Dashboard.NodeDiscovery | |||
{ | |||
public class Node | |||
{ | |||
public string Id { get; set; } | |||
public string Name { get; set; } | |||
public string Address { get; set; } | |||
public int Port { get; set; } | |||
public string Tags { get; set; } | |||
} | |||
} |