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

Use Elasticsearch cluster health for check instead of ping #290

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 30 additions & 5 deletions src/HealthChecks.Elasticsearch/ElasticsearchHealthCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,37 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
}
}

var pingResult = await lowLevelClient.PingAsync(ct: cancellationToken);
var isSuccess = pingResult.ApiCall.HttpStatusCode == 200;
if (_options.CheckClusterHealth)
{

var healthResult = await lowLevelClient.Cluster.HealthAsync(ct: cancellationToken);

if (healthResult.ApiCall.HttpStatusCode != 200)
{
return new HealthCheckResult(context.Registration.FailureStatus);
}

switch (healthResult.Status.ToString())
{
case "Green":
return HealthCheckResult.Healthy();

return isSuccess
? HealthCheckResult.Healthy()
: new HealthCheckResult(context.Registration.FailureStatus);
case "Yellow":
return HealthCheckResult.Degraded();

default:
return HealthCheckResult.Unhealthy();
}
}
else
{
var pingResult = await lowLevelClient.PingAsync(ct: cancellationToken);
var isSuccess = pingResult.ApiCall.HttpStatusCode == 200;

return isSuccess
? HealthCheckResult.Healthy()
: new HealthCheckResult(context.Registration.FailureStatus);
}
}
catch (Exception ex)
{
Expand Down
8 changes: 7 additions & 1 deletion src/HealthChecks.Elasticsearch/ElasticsearchOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class ElasticsearchOptions
public X509Certificate Certificate { get; private set; }
public bool AuthenticateWithBasicCredentials { get; private set; } = false;
public bool AuthenticateWithCertificate { get; private set; } = false;
public bool CheckClusterHealth { get; private set; } = false;
inkel marked this conversation as resolved.
Show resolved Hide resolved
public Func<object, X509Certificate, X509Chain, SslPolicyErrors, bool> CertificateValidationCallback { get; private set; }
public ElasticsearchOptions UseBasicAuthentication(string name, string password)
{
Expand Down Expand Up @@ -44,5 +45,10 @@ public ElasticsearchOptions UseCertificateValidationCallback(Func<object, X509Ce
CertificateValidationCallback = callback;
return this;
}
public ElasticsearchOptions UseClusterHealth()
{
CheckClusterHealth = true;
return this;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using System;
Expand Down Expand Up @@ -77,5 +78,118 @@ public async Task be_unhealthy_if_elasticsearch_is_not_available()
response.StatusCode
.Should().Be(HttpStatusCode.ServiceUnavailable);
}

[SkipOnAppVeyor]
public async Task be_unhealthy_if_elasticsearch_cluster_health_status_is_red()
{
await RunWithMockElasticsearch("red", async server =>
{
var response = await server.CreateRequest("/health").GetAsync();

var body = await response.Content.ReadAsStringAsync();

response.StatusCode.Should().Be(HttpStatusCode.ServiceUnavailable);

body.Should().Be("Unhealthy");
});
}

[SkipOnAppVeyor]
public async Task be_healthy_if_elasticsearch_cluster_health_status_is_green()
{
await RunWithMockElasticsearch("green", async server =>
{
var response = await server.CreateRequest("/health").GetAsync();

var body = await response.Content.ReadAsStringAsync();

body.Should().Be("Healthy");

response.StatusCode.Should().Be(HttpStatusCode.OK);
});
}

[SkipOnAppVeyor]
public async Task be_degraded_if_elasticsearch_cluster_health_status_is_yellow()
{
await RunWithMockElasticsearch("yellow", async server =>
{
var response = await server.CreateRequest("/health").GetAsync();

var body = await response.Content.ReadAsStringAsync();

body.Should().Be("Degraded");

response.StatusCode.Should().Be(HttpStatusCode.OK);
});
}

private async Task RunWithMockElasticsearch(string clusterStatus, Func<TestServer, Task> action)
{
string clusterHealthJsonResponse = @"{{
""cluster_name"" : ""docker-cluster"",
""status"" : ""{0}"",
""timed_out"" : false,
""number_of_nodes"" : 1,
""number_of_data_nodes"" : 1,
""active_primary_shards"" : 12,
""active_shards"" : 12,
""relocating_shards"" : 0,
""initializing_shards"" : 0,
""unassigned_shards"" : 0,
""delayed_unassigned_shards"" : 0,
""number_of_pending_tasks"" : 0,
""number_of_in_flight_fetch"" : 0,
""task_max_waiting_in_queue_millis"" : 0,
""active_shards_percent_as_number"" : 100.00
}}";
var port = new Random().Next(19200, 19299);

var elasticsearchUrl = $"http://localhost:{port}";

var elasticsearchHostBuilder = new WebHostBuilder()
.UseStartup<DefaultStartup>()
.UseKestrel()
.UseUrls(elasticsearchUrl)
.Configure(app =>
{
var response = string.Format(clusterHealthJsonResponse, clusterStatus);
app.Map("/_cluster/health", c => c.Run(async ctx =>
{
ctx.Response.StatusCode = (int)HttpStatusCode.OK; // Elasticsearch always return 200 OK for this API
ctx.Response.ContentType = "application/json";
await ctx.Response.WriteAsync(response);
}));
});

using (var elastichsearchHost = elasticsearchHostBuilder.Build())
{
await elastichsearchHost.StartAsync();

var webHostBuilder = new WebHostBuilder()
.UseStartup<DefaultStartup>()
.ConfigureServices(services =>
{
services.AddHealthChecks()
.AddElasticsearch(setup =>
{
setup.UseServer(elasticsearchUrl)
.UseClusterHealth();
}, tags: new string[] { "elasticsearch" });
})
.Configure(app =>
app.UseHealthChecks("/health", new HealthCheckOptions()
{
Predicate = r => r.Tags.Contains("elasticsearch")
}));

using (var server = new TestServer(webHostBuilder))
{
await action(server);
}

await elastichsearchHost.StopAsync();
}
}
}
}