mirror of
https://github.com/TechnitiumSoftware/DnsServer.git
synced 2026-10-12 02:09:51 +00:00
767 lines
25 KiB
C#
767 lines
25 KiB
C#
/*
|
|
Technitium DNS Server
|
|
Copyright (C) 2026 Shreyas Zare ([email protected])
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
using DnsServerCore.Auth;
|
|
using DnsServerCore.HttpApi;
|
|
using DnsServerCore.HttpApi.Models;
|
|
using Microsoft.AspNetCore.Http;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using TechnitiumLibrary;
|
|
using TechnitiumLibrary.IO;
|
|
using TechnitiumLibrary.Net;
|
|
|
|
namespace DnsServerCore.Cluster
|
|
{
|
|
enum ClusterNodeType : byte
|
|
{
|
|
Unknown = 0,
|
|
Primary = 1,
|
|
Secondary = 2
|
|
}
|
|
|
|
enum ClusterNodeState : byte
|
|
{
|
|
Unknown = 0,
|
|
Self = 1,
|
|
Connected = 2,
|
|
Unreachable = 3
|
|
}
|
|
|
|
class ClusterNode : IComparable<ClusterNode>, IDisposable
|
|
{
|
|
#region variables
|
|
|
|
readonly ClusterManager _clusterManager;
|
|
|
|
readonly int _id;
|
|
Uri _url;
|
|
IReadOnlyList<IPAddress> _ipAddresses;
|
|
ClusterNodeType _type;
|
|
ClusterNodeState _state;
|
|
|
|
string _version;
|
|
DateTime _upSince;
|
|
DateTime _lastSeen;
|
|
HttpApiClient _apiClient;
|
|
|
|
Timer _heartbeatTimer;
|
|
const int HEARTBEAT_TIMER_INITIAL_INTERVAL = 5000;
|
|
|
|
#endregion
|
|
|
|
#region constructor
|
|
|
|
public ClusterNode(ClusterManager clusterManager, ClusterInfo.ClusterNodeInfo nodeInfo)
|
|
{
|
|
_clusterManager = clusterManager;
|
|
|
|
_id = nodeInfo.Id;
|
|
_url = nodeInfo.Url;
|
|
_ipAddresses = nodeInfo.IPAddresses.Convert(IPAddress.Parse);
|
|
_type = Enum.Parse<ClusterNodeType>(nodeInfo.Type, true);
|
|
|
|
if (_type == ClusterNodeType.Primary)
|
|
{
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected; //since this info was received from primary node
|
|
}
|
|
else
|
|
{
|
|
_state = ClusterNodeState.Unknown;
|
|
}
|
|
}
|
|
|
|
public ClusterNode(ClusterManager clusterManager, int id, Uri url, IReadOnlyList<IPAddress> ipAddresses, ClusterNodeType type, ClusterNodeState state)
|
|
{
|
|
if (url.OriginalString.Length > 255)
|
|
throw new ArgumentException("Cluster node URL length must be less than 255 bytes.", nameof(url));
|
|
|
|
if (!url.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
|
|
throw new ArgumentException("Cluster node URL must use HTTPS scheme.", nameof(url));
|
|
|
|
if (ipAddresses.Count > 10)
|
|
throw new ArgumentException("Cluster node cannot have more than 10 IP addresses.", nameof(ipAddresses));
|
|
|
|
_clusterManager = clusterManager;
|
|
|
|
_id = id;
|
|
_url = url;
|
|
_ipAddresses = ipAddresses;
|
|
_type = type;
|
|
_state = state;
|
|
}
|
|
|
|
public ClusterNode(ClusterManager clusterManager, BinaryReader bR)
|
|
{
|
|
_clusterManager = clusterManager;
|
|
|
|
int version = bR.ReadByte();
|
|
switch (version)
|
|
{
|
|
case 1:
|
|
case 2:
|
|
_id = bR.ReadInt32();
|
|
_url = new Uri(bR.BaseStream.ReadShortString());
|
|
|
|
if (version >= 2)
|
|
{
|
|
int count = bR.ReadByte();
|
|
IPAddress[] ipAddresses = new IPAddress[count];
|
|
|
|
for (int i = 0; i < count; i++)
|
|
ipAddresses[i] = IPAddressExtensions.ReadFrom(bR);
|
|
|
|
_ipAddresses = ipAddresses;
|
|
}
|
|
else
|
|
{
|
|
_ipAddresses = [IPAddressExtensions.ReadFrom(bR)];
|
|
}
|
|
|
|
_type = (ClusterNodeType)bR.ReadByte();
|
|
_state = (ClusterNodeState)bR.ReadByte();
|
|
break;
|
|
|
|
default:
|
|
throw new InvalidDataException("Cluster Node version not supported.");
|
|
}
|
|
|
|
if (_state != ClusterNodeState.Self)
|
|
_state = ClusterNodeState.Unknown;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
bool _disposed;
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
_heartbeatTimer?.Dispose();
|
|
|
|
if (_apiClient is not null)
|
|
{
|
|
ThreadPool.QueueUserWorkItem(async delegate (object state)
|
|
{
|
|
try
|
|
{
|
|
await Task.Delay(2000); //give some time for any in-progress API calls to complete
|
|
_apiClient?.Dispose();
|
|
}
|
|
catch
|
|
{ }
|
|
});
|
|
}
|
|
|
|
_disposed = true;
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region private
|
|
|
|
private HttpApiClient GetApiClient()
|
|
{
|
|
if (_state == ClusterNodeState.Self)
|
|
throw new InvalidOperationException();
|
|
|
|
if (_apiClient is null)
|
|
{
|
|
HttpApiClient apiClient = new HttpApiClient(_url, _clusterManager.DnsWebService.DnsServer.Proxy, _clusterManager.DnsWebService.DnsServer.IPv6Mode, false, new InternalDnsClient(_clusterManager.DnsWebService.DnsServer, this));
|
|
|
|
UserSession clusterApiToken = null;
|
|
|
|
foreach (UserSession session in _clusterManager.DnsWebService.AuthManager.Sessions)
|
|
{
|
|
if (session.Type == UserSessionType.ClusterApiToken)
|
|
{
|
|
clusterApiToken = session;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (clusterApiToken is null)
|
|
throw new InvalidOperationException("No API token was found for the Cluster domain.");
|
|
|
|
apiClient.UseApiToken(clusterApiToken.Token);
|
|
|
|
_apiClient = apiClient;
|
|
}
|
|
|
|
return _apiClient;
|
|
}
|
|
|
|
private async void HeartbeatTimerCallbackAsync(object state)
|
|
{
|
|
bool success = true;
|
|
|
|
try
|
|
{
|
|
ClusterInfo clusterInfo = await GetClusterStateAsync();
|
|
|
|
if (_type == ClusterNodeType.Primary)
|
|
_clusterManager.UpdateClusterFromPrimaryNode(clusterInfo); //update cluster nodes from primary node response
|
|
|
|
//update version & up since time
|
|
foreach (ClusterInfo.ClusterNodeInfo clusterNodeInfo in clusterInfo.ClusterNodes)
|
|
{
|
|
if (clusterNodeInfo.Name.Equals(Name, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
_version = clusterNodeInfo.Version;
|
|
_upSince = clusterNodeInfo.UpSince ?? default;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
//ignore
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
success = false;
|
|
_clusterManager.DnsWebService.LogManager.Write("Heartbeat failed for " + _type.ToString() + " node '" + ToString() + "'.", ex);
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
_heartbeatTimer?.Change(success ? _clusterManager.HeartbeatRefreshIntervalSeconds * 1000 : _clusterManager.HeartBeatRetryIntervalSeconds * 1000, Timeout.Infinite);
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{ }
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region public
|
|
|
|
public void PromoteToPrimaryNode()
|
|
{
|
|
_type = ClusterNodeType.Primary;
|
|
}
|
|
|
|
public void UpdateSelfNodeIPAddresses(IReadOnlyList<IPAddress> ipAddresses)
|
|
{
|
|
if (_state != ClusterNodeState.Self)
|
|
throw new InvalidOperationException();
|
|
|
|
if (ipAddresses.Count > 10)
|
|
throw new ArgumentException("Cluster node cannot have more than 10 IP addresses.", nameof(ipAddresses));
|
|
|
|
_ipAddresses = ipAddresses;
|
|
}
|
|
|
|
public void UpdateSelfNodeUrl()
|
|
{
|
|
if (_state != ClusterNodeState.Self)
|
|
throw new InvalidOperationException();
|
|
|
|
Uri url = new Uri($"https://{_clusterManager.DnsWebService.DnsServer.ServerDomain}:{_clusterManager.DnsWebService.WebServiceTlsPort}/");
|
|
|
|
if (url.OriginalString.Length > 255)
|
|
throw new ArgumentException("Cluster node URL length must be less than 255 bytes.", nameof(url));
|
|
|
|
if (!url.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
|
|
throw new ArgumentException("Cluster node URL must use HTTPS scheme.", nameof(url));
|
|
|
|
_url = url;
|
|
}
|
|
|
|
public void UpdateNode(Uri url, IReadOnlyList<IPAddress> ipAddresses)
|
|
{
|
|
if (url.OriginalString.Length > 255)
|
|
throw new ArgumentException("Cluster node URL length must be less than 255 bytes.", nameof(url));
|
|
|
|
if (!url.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
|
|
throw new ArgumentException("Cluster node URL must use HTTPS scheme.", nameof(url));
|
|
|
|
if (ipAddresses.Count > 10)
|
|
throw new ArgumentException("Cluster node cannot have more than 10 IP addresses.", nameof(ipAddresses));
|
|
|
|
bool changed = false;
|
|
|
|
if (!_url.Equals(url))
|
|
{
|
|
_url = url;
|
|
changed = true;
|
|
}
|
|
|
|
if (!_ipAddresses.HasSameItems(ipAddresses))
|
|
{
|
|
_ipAddresses = ipAddresses;
|
|
changed = true;
|
|
}
|
|
|
|
if (changed && (_apiClient is not null))
|
|
{
|
|
_apiClient.Dispose();
|
|
_apiClient = null;
|
|
}
|
|
}
|
|
|
|
public void UpdateNode(ClusterInfo.ClusterNodeInfo nodeInfo)
|
|
{
|
|
if (nodeInfo.Id != _id)
|
|
throw new InvalidOperationException();
|
|
|
|
bool changed = false;
|
|
|
|
if (!_url.Equals(nodeInfo.Url))
|
|
{
|
|
_url = nodeInfo.Url;
|
|
changed = true;
|
|
}
|
|
|
|
IReadOnlyList<IPAddress> ipAddresses = nodeInfo.IPAddresses.Convert(IPAddress.Parse);
|
|
if (!_ipAddresses.HasSameItems(ipAddresses))
|
|
{
|
|
_ipAddresses = ipAddresses;
|
|
changed = true;
|
|
}
|
|
|
|
_type = Enum.Parse<ClusterNodeType>(nodeInfo.Type, true);
|
|
|
|
if (changed && (_apiClient is not null))
|
|
{
|
|
_apiClient.Dispose();
|
|
_apiClient = null;
|
|
}
|
|
}
|
|
|
|
public void InitializeHeartbeatTimer()
|
|
{
|
|
if (_state == ClusterNodeState.Self)
|
|
throw new InvalidOperationException();
|
|
|
|
if (_heartbeatTimer is null)
|
|
{
|
|
_heartbeatTimer = new Timer(HeartbeatTimerCallbackAsync);
|
|
|
|
//for Primary node use configured refresh interval since config transfer already syncs the cluster state
|
|
_heartbeatTimer.Change(_type == ClusterNodeType.Primary ? _clusterManager.HeartbeatRefreshIntervalSeconds * 1000 : HEARTBEAT_TIMER_INITIAL_INTERVAL, Timeout.Infinite);
|
|
}
|
|
}
|
|
|
|
public void UpdateHeartbeatTimer()
|
|
{
|
|
if (_state == ClusterNodeState.Self)
|
|
throw new InvalidOperationException();
|
|
|
|
_heartbeatTimer?.Change(_clusterManager.HeartbeatRefreshIntervalSeconds * 1000, Timeout.Infinite);
|
|
}
|
|
|
|
public async Task<DashboardStats> GetDashboardStatsAsync(User sessionUser, DashboardStatsType type = DashboardStatsType.LastHour, bool utcFormat = false, string acceptLanguage = "en-US,en;q=0.5", bool dontTrimQueryTypeData = false, DateTime startDate = default, DateTime endDate = default, CancellationToken cancellationToken = default)
|
|
{
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
DashboardStats stats = await apiClient.GetDashboardStatsAsync(sessionUser.Username, type, utcFormat, acceptLanguage, dontTrimQueryTypeData, startDate, endDate, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
|
|
return stats;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<DashboardStats> GetDashboardTopStatsAsync(User sessionUser, DashboardTopStatsType statsType, int limit = 1000, DashboardStatsType type = DashboardStatsType.LastHour, DateTime startDate = default, DateTime endDate = default, CancellationToken cancellationToken = default)
|
|
{
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
DashboardStats stats = await apiClient.GetDashboardTopStatsAsync(sessionUser.Username, statsType, limit, type, startDate, endDate, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
|
|
return stats;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task SetClusterSettingsAsync(User sessionUser, IReadOnlyDictionary<string, string> clusterParameters, CancellationToken cancellationToken = default)
|
|
{
|
|
if (_type != ClusterNodeType.Primary)
|
|
throw new InvalidOperationException();
|
|
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
await apiClient.SetClusterSettingsAsync(sessionUser.Username, clusterParameters, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task ForceUpdateBlockListsAsync(User sessionUser, CancellationToken cancellationToken = default)
|
|
{
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
await apiClient.ForceUpdateBlockListsAsync(sessionUser.Username, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task TemporaryDisableBlockingAsync(User sessionUser, int minutes, CancellationToken cancellationToken = default)
|
|
{
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
await apiClient.TemporaryDisableBlockingAsync(sessionUser.Username, minutes, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task CreateSsoUserAsync(string ssoIdentifier, string username, string displayName = null, IReadOnlyCollection<string> memberOfGroups = null, CancellationToken cancellationToken = default)
|
|
{
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
await apiClient.CreateSsoUserAsync(ssoIdentifier, username, displayName, memberOfGroups, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task SetSsoUserAsync(string username, string newUsername = null, string displayName = null, IReadOnlyCollection<string> memberOfGroups = null, CancellationToken cancellationToken = default)
|
|
{
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
await apiClient.SetSsoUserAsync(username, newUsername, displayName, memberOfGroups, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<ClusterInfo> GetClusterStateAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
ClusterInfo clusterInfo = await apiClient.GetClusterStateAsync(false, true, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
|
|
return clusterInfo;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<ClusterInfo> DeleteClusterAsync(bool forceDelete = false, CancellationToken cancellationToken = default)
|
|
{
|
|
if (_type != ClusterNodeType.Primary)
|
|
throw new InvalidOperationException();
|
|
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
ClusterInfo clusterInfo = await apiClient.DeleteClusterAsync(forceDelete, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Unreachable; //node is deleted, so mark as unreachable
|
|
|
|
return clusterInfo;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task NotifySecondaryNodeAsync(ClusterNode primaryNode, CancellationToken cancellationToken = default)
|
|
{
|
|
if (_type != ClusterNodeType.Secondary)
|
|
throw new InvalidOperationException();
|
|
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
await apiClient.NotifySecondaryNodeAsync(primaryNode.Id, primaryNode._url, primaryNode._ipAddresses, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
|
|
_clusterManager.DnsWebService.LogManager.Write("DNS Server successfully notified Secondary node '" + ToString() + "' for server configuration changes.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
|
|
_clusterManager.DnsWebService.LogManager.Write("DNS Server failed to notify Secondary node '" + ToString() + "' for server configuration changes.", ex);
|
|
}
|
|
}
|
|
|
|
public async Task SyncConfigAsync(IReadOnlyCollection<string> includeZones = null, CancellationToken cancellationToken = default)
|
|
{
|
|
if (_type != ClusterNodeType.Primary)
|
|
throw new InvalidOperationException();
|
|
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
await _clusterManager.SyncConfigFromAsync(apiClient, includeZones, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task AskSecondaryNodeToLeaveClusterAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
if (_type != ClusterNodeType.Secondary)
|
|
throw new InvalidOperationException();
|
|
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
_ = await apiClient.LeaveClusterAsync(cancellationToken: cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task DeleteSecondaryNodeAsync(ClusterNode secondaryNode, CancellationToken cancellationToken = default)
|
|
{
|
|
if (_type != ClusterNodeType.Primary)
|
|
throw new InvalidOperationException();
|
|
|
|
if (secondaryNode.Type != ClusterNodeType.Secondary)
|
|
throw new InvalidOperationException();
|
|
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
await apiClient.DeleteSecondaryNodeAsync(secondaryNode._id, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<ClusterInfo> UpdateSecondaryNodeAsync(ClusterNode secondaryNode, X509Certificate2 secondaryNodeCertificate, CancellationToken cancellationToken = default)
|
|
{
|
|
if (_type != ClusterNodeType.Primary)
|
|
throw new InvalidOperationException();
|
|
|
|
if (secondaryNode.Type != ClusterNodeType.Secondary)
|
|
throw new InvalidOperationException();
|
|
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
ClusterInfo clusterInfo = await apiClient.UpdateSecondaryNodeAsync(secondaryNode._id, secondaryNode._url, secondaryNode._ipAddresses, secondaryNodeCertificate, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
|
|
return clusterInfo;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task ProxyRequest(HttpContext context, string actingUsername, CancellationToken cancellationToken = default)
|
|
{
|
|
HttpApiClient apiClient = GetApiClient();
|
|
|
|
try
|
|
{
|
|
await apiClient.ProxyRequest(context, actingUsername, cancellationToken);
|
|
|
|
_lastSeen = DateTime.UtcNow;
|
|
_state = ClusterNodeState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
_state = ClusterNodeState.Unreachable;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public void WriteTo(BinaryWriter bW)
|
|
{
|
|
bW.Write((byte)2); //version
|
|
bW.Write(_id);
|
|
bW.BaseStream.WriteShortString(_url.OriginalString);
|
|
|
|
bW.Write(Convert.ToByte(_ipAddresses.Count));
|
|
|
|
foreach (IPAddress ipAddress in _ipAddresses)
|
|
ipAddress.WriteTo(bW);
|
|
|
|
bW.Write((byte)_type);
|
|
bW.Write((byte)_state);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return _url.Host.ToLowerInvariant() + " (" + _ipAddresses.Join() + ")";
|
|
}
|
|
|
|
public int CompareTo(ClusterNode other)
|
|
{
|
|
return _url.Host.CompareTo(other._url.Host);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region properties
|
|
|
|
public int Id
|
|
{ get { return _id; } }
|
|
|
|
public string Name
|
|
{ get { return _url.Host.ToLowerInvariant(); } }
|
|
|
|
public Uri Url
|
|
{ get { return _url; } }
|
|
|
|
public IReadOnlyList<IPAddress> IPAddresses
|
|
{ get { return _ipAddresses; } }
|
|
|
|
public ClusterNodeType Type
|
|
{ get { return _type; } }
|
|
|
|
public ClusterNodeState State
|
|
{ get { return _state; } }
|
|
|
|
public string Version
|
|
{
|
|
get
|
|
{
|
|
if (_state == ClusterNodeState.Self)
|
|
return _clusterManager.DnsWebService.Version;
|
|
|
|
return _version;
|
|
}
|
|
}
|
|
|
|
public DateTime UpSince
|
|
{
|
|
get
|
|
{
|
|
if (_state == ClusterNodeState.Self)
|
|
return _clusterManager.DnsWebService.UpTimeStamp;
|
|
|
|
return _upSince;
|
|
}
|
|
}
|
|
|
|
public DateTime LastSeen
|
|
{ get { return _lastSeen; } }
|
|
|
|
#endregion
|
|
}
|
|
}
|