Failover: updated implementation to not perform health check on server down addresses.

This commit is contained in:
Shreyas Zare
2021-08-21 12:29:19 +05:30
parent d7c0bd5272
commit c70a2365a2
2 changed files with 87 additions and 35 deletions

View File

@@ -34,15 +34,14 @@ namespace Failover
{
Unknown = 0,
Primary = 1,
Secondary = 2,
ServerDown = 3
Secondary = 2
}
public class Address : IDnsApplicationRequestHandler
{
#region variables
HealthMonitoringService _healthService;
HealthService _healthService;
#endregion
@@ -116,7 +115,7 @@ namespace Failover
IPAddress address = IPAddress.Parse(jsonAddress.Value);
HealthCheckStatus status = _healthService.QueryStatus(address, healthCheck, healthCheckUrl, false);
string text = "app=failover; addressType=" + type.ToString() + "; address=" + address.ToString() + "; healthCheck=" + healthCheck;
string text = "app=failover; addressType=" + type.ToString() + "; address=" + address.ToString() + "; healthCheck=" + healthCheck + (healthCheckUrl is null ? "" : "; healthCheckUrl=" + healthCheckUrl.AbsoluteUri);
if (status is null)
text += "; healthStatus=Unknown;";
@@ -136,7 +135,7 @@ namespace Failover
public Task InitializeAsync(IDnsServer dnsServer, string config)
{
if (_healthService is null)
_healthService = HealthMonitoringService.Create(dnsServer);
_healthService = HealthService.Create(dnsServer);
_healthService.Initialize(JsonConvert.DeserializeObject(config));
@@ -156,11 +155,21 @@ namespace Failover
string healthCheck = jsonAppRecordData.healthCheck?.Value;
Uri healthCheckUrl = null;
if ((jsonAppRecordData.healthCheckUrl is not null) && (jsonAppRecordData.healthCheckUrl.Value is not null))
healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value);
if (healthCheckUrl is null)
healthCheckUrl = new Uri("http://" + question.Name);
if (_healthService.HealthChecks.TryGetValue(healthCheck, out HealthCheck hc) && ((hc.Type == HealthCheckType.Https) || (hc.Type == HealthCheckType.Http)) && (hc.Url is null))
{
//read health check url only for http/https type checks and only when app config does not have an url configured
if ((jsonAppRecordData.healthCheckUrl is not null) && (jsonAppRecordData.healthCheckUrl.Value is not null))
{
healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value);
}
else
{
if (hc.Type == HealthCheckType.Https)
healthCheckUrl = new Uri("https://" + question.Name);
else
healthCheckUrl = new Uri("http://" + question.Name);
}
}
List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
@@ -170,7 +179,30 @@ namespace Failover
GetAnswers(jsonAppRecordData.secondary, question, appRecordTtl, healthCheck, healthCheckUrl, answers);
if (answers.Count == 0)
{
GetAnswers(jsonAppRecordData.serverDown, question, appRecordTtl, healthCheck, healthCheckUrl, answers);
if (jsonAppRecordData.serverDown is not null)
{
if (question.Type == DnsResourceRecordType.A)
{
foreach (dynamic jsonAddress in jsonAppRecordData.serverDown)
{
IPAddress address = IPAddress.Parse(jsonAddress.Value);
if (address.AddressFamily == AddressFamily.InterNetwork)
answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.A, question.Class, 30, new DnsARecord(address)));
}
}
else
{
foreach (dynamic jsonAddress in jsonAppRecordData.serverDown)
{
IPAddress address = IPAddress.Parse(jsonAddress.Value);
if (address.AddressFamily == AddressFamily.InterNetworkV6)
answers.Add(new DnsResourceRecord(question.Name, DnsResourceRecordType.AAAA, question.Class, 30, new DnsAAAARecord(address)));
}
}
}
if (answers.Count == 0)
return Task.FromResult<DnsDatagram>(null);
}
@@ -199,17 +231,26 @@ namespace Failover
string healthCheck = jsonAppRecordData.healthCheck?.Value;
Uri healthCheckUrl = null;
if ((jsonAppRecordData.healthCheckUrl is not null) && (jsonAppRecordData.healthCheckUrl.Value is not null))
healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value);
if (healthCheckUrl is null)
healthCheckUrl = new Uri("http://" + question.Name);
if (_healthService.HealthChecks.TryGetValue(healthCheck, out HealthCheck hc) && ((hc.Type == HealthCheckType.Https) || (hc.Type == HealthCheckType.Http)) && (hc.Url is null))
{
//read health check url only for http/https type checks and only when app config does not have an url configured
if ((jsonAppRecordData.healthCheckUrl is not null) && (jsonAppRecordData.healthCheckUrl.Value is not null))
{
healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value);
}
else
{
if (hc.Type == HealthCheckType.Https)
healthCheckUrl = new Uri("https://" + question.Name);
else
healthCheckUrl = new Uri("http://" + question.Name);
}
}
List<DnsResourceRecord> answers = new List<DnsResourceRecord>();
GetStatusAnswers(jsonAppRecordData.primary, FailoverType.Primary, question, 30, healthCheck, healthCheckUrl, answers);
GetStatusAnswers(jsonAppRecordData.secondary, FailoverType.Secondary, question, 30, healthCheck, healthCheckUrl, answers);
GetStatusAnswers(jsonAppRecordData.serverDown, FailoverType.ServerDown, question, 30, healthCheck, healthCheckUrl, answers);
return Task.FromResult(new DnsDatagram(request.Identifier, true, request.OPCODE, true, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question, answers));
}
@@ -224,7 +265,7 @@ namespace Failover
#region properties
public string Description
{ get { return "Returns A or AAAA records from primary set of addresses with a continous health check as configured in the app config. When none of the primary addresses are healthy, the app returns healthy addresses from the secondary set of addresses. When none of the primary and secondary addresses are healthy, the app returns healthy addresses from the server down set of addresses. The server down feature is expected to be used for showing a service status page and not to serve the actual content.\n\nWhen an URL is not provided in 'healthCheckUrl' parameter for 'http' or 'https' type health check, the domain name of the APP record will be used to auto generate an URL.\n\nSet 'allowTxtStatus' parameter to 'true' in your APP record data to allow checking health status by querying for TXT record."; } }
{ get { return "Returns A or AAAA records from primary set of addresses with a continous health check as configured in the app config. When none of the primary addresses are healthy, the app returns healthy addresses from the secondary set of addresses. When none of the primary and secondary addresses are healthy, the app returns all addresses from the server down set of addresses. The server down feature is expected to be used for showing a service status page and not to serve the actual content.\n\nIf an URL is provided for the health check in the app's config then it will override the 'healthCheckUrl' parameter. When an URL is not provided in 'healthCheckUrl' parameter for 'http' or 'https' type health check, the domain name of the APP record will be used to auto generate an URL.\n\nSet 'allowTxtStatus' parameter to 'true' in your APP record data to allow checking health status by querying for TXT record."; } }
public string ApplicationRecordDataTemplate
{
@@ -243,7 +284,7 @@ namespace Failover
""3.3.3.3""
],
""healthCheck"": ""https"",
""healthCheckUrl"": null,
""healthCheckUrl"": ""https://www.example.com/"",
""allowTxtStatus"": false
}";
}

View File

@@ -32,7 +32,7 @@ namespace Failover
{
#region variables
HealthMonitoringService _healthService;
HealthService _healthService;
#endregion
@@ -81,7 +81,7 @@ namespace Failover
{
HealthCheckStatus status = _healthService.QueryStatus(domain, DnsResourceRecordType.A, healthCheck, healthCheckUrl, false);
string text = "app=failover; cnameType=" + type.ToString() + "; domain=" + domain + "; qType: A; healthCheck=" + healthCheck;
string text = "app=failover; cnameType=" + type.ToString() + "; domain=" + domain + "; qType: A; healthCheck=" + healthCheck + (healthCheckUrl is null ? "" : "; healthCheckUrl=" + healthCheckUrl.AbsoluteUri);
if (status is null)
text += "; healthStatus=Unknown;";
@@ -96,7 +96,7 @@ namespace Failover
{
HealthCheckStatus status = _healthService.QueryStatus(domain, DnsResourceRecordType.AAAA, healthCheck, healthCheckUrl, false);
string text = "app=failover; cnameType=" + type.ToString() + "; domain=" + domain + "; qType: AAAA; healthCheck=" + healthCheck;
string text = "app=failover; cnameType=" + type.ToString() + "; domain=" + domain + "; qType: AAAA; healthCheck=" + healthCheck + (healthCheckUrl is null ? "" : "; healthCheckUrl=" + healthCheckUrl.AbsoluteUri);
if (status is null)
text += "; healthStatus=Unknown;";
@@ -116,7 +116,7 @@ namespace Failover
public Task InitializeAsync(IDnsServer dnsServer, string config)
{
if (_healthService is null)
_healthService = HealthMonitoringService.Create(dnsServer);
_healthService = HealthService.Create(dnsServer);
//let Address class initialize config
@@ -132,11 +132,21 @@ namespace Failover
string healthCheck = jsonAppRecordData.healthCheck?.Value;
Uri healthCheckUrl = null;
if ((jsonAppRecordData.healthCheckUrl is not null) && (jsonAppRecordData.healthCheckUrl.Value is not null))
healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value);
if (healthCheckUrl is null)
healthCheckUrl = new Uri("http://" + question.Name);
if (_healthService.HealthChecks.TryGetValue(healthCheck, out HealthCheck hc) && ((hc.Type == HealthCheckType.Https) || (hc.Type == HealthCheckType.Http)) && (hc.Url is null))
{
//read health check url only for http/https type checks and only when app config does not have an url configured
if ((jsonAppRecordData.healthCheckUrl is not null) && (jsonAppRecordData.healthCheckUrl.Value is not null))
{
healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value);
}
else
{
if (hc.Type == HealthCheckType.Https)
healthCheckUrl = new Uri("https://" + question.Name);
else
healthCheckUrl = new Uri("http://" + question.Name);
}
}
IReadOnlyList<DnsResourceRecord> answers;
@@ -159,8 +169,6 @@ namespace Failover
foreach (dynamic jsonDomain in jsonAppRecordData.secondary)
GetStatusAnswers(jsonDomain.Value, FailoverType.Secondary, question, 30, healthCheck, healthCheckUrl, txtAnswers);
GetStatusAnswers(jsonAppRecordData.serverDown.Value, FailoverType.ServerDown, question, 30, healthCheck, healthCheckUrl, txtAnswers);
answers = txtAnswers;
}
else
@@ -177,12 +185,15 @@ namespace Failover
if (answers is null)
{
if (jsonAppRecordData.serverDown == null)
if ((jsonAppRecordData.serverDown is null) || (jsonAppRecordData.serverDown.Value is null))
return Task.FromResult<DnsDatagram>(null);
answers = GetAnswers(jsonAppRecordData.serverDown.Value, question, zoneName, appRecordTtl, healthCheck, healthCheckUrl);
if (answers is null)
return Task.FromResult<DnsDatagram>(null);
string serverDown = jsonAppRecordData.serverDown.Value;
if (question.Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase)) //check for zone apex
answers = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.ANAME, DnsClass.IN, 30, new DnsANAMERecord(serverDown)) }; //use ANAME
else
answers = new DnsResourceRecord[] { new DnsResourceRecord(question.Name, DnsResourceRecordType.CNAME, DnsClass.IN, 30, new DnsCNAMERecord(serverDown)) };
}
}
}
@@ -195,7 +206,7 @@ namespace Failover
#region properties
public string Description
{ get { return "Returns CNAME record for primary domain name with a continous health check as configured in the app config. When the primary domain name is unhealthy, the app returns one of the secondary domain names in the given order of preference that is healthy. When none of the primary and secondary domain names are healthy, the app returns the server down domain name. The server down feature is expected to be used for showing a service status page and not to serve the actual content. Note that the app will return ANAME record for an APP record at zone apex.\n\nWhen an URL is not provided in 'healthCheckUrl' parameter for 'http' or 'https' type health check, the domain name of the APP record will be used to auto generate an URL.\n\nSet 'allowTxtStatus' parameter to 'true' in your APP record data to allow checking health status by querying for TXT record."; } }
{ get { return "Returns CNAME record for primary domain name with a continous health check as configured in the app config. When the primary domain name is unhealthy, the app returns one of the secondary domain names in the given order of preference that is healthy. When none of the primary and secondary domain names are healthy, the app returns the server down domain name. The server down feature is expected to be used for showing a service status page and not to serve the actual content. Note that the app will return ANAME record for an APP record at zone apex.\n\nIf an URL is provided for the health check in the app's config then it will override the 'healthCheckUrl' parameter. When an URL is not provided in 'healthCheckUrl' parameter for 'http' or 'https' type health check, the domain name of the APP record will be used to auto generate an URL.\n\nSet 'allowTxtStatus' parameter to 'true' in your APP record data to allow checking health status by querying for TXT record."; } }
public string ApplicationRecordDataTemplate
{