Skip to content

Commit acbb2f3

Browse files
manojsardanasardanamcedriczieljulianocosta89
authored andcommitted
Add Grpc Readiness probe to cart microservice. Failure can be enabled through feature flag to simulate pod not ready situation (open-telemetry#2830)
* Adding readiness probe health check to cart service + feature flag added to enable it in flagd * Clean up commented health check code Removed commented-out health check code in Program.cs. * Update src/cart/src/Program.cs Co-authored-by: Cedric Ziel <mail@cedric-ziel.com> * moving the healthcheck test to a separate file under services folder * Fix missing newline in healthcheck.cs Add missing newline at end of file * Add copyright and license information to healthcheck.cs * changelog --------- Co-authored-by: Manojkumar Sardana <sardanam@hcl.com> Co-authored-by: Cedric Ziel <mail@cedric-ziel.com> Co-authored-by: Juliano Costa <julianocosta89@outlook.com> Co-authored-by: Juliano Costa <juliano.costa@datadoghq.com>
1 parent ead0e9a commit acbb2f3

File tree

5 files changed

+147
-2
lines changed

5 files changed

+147
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,5 @@ test/tracetesting/tracetesting-vars.yaml
5353
*.apk
5454

5555
!src/currency/build
56+
src/cart/src/Program.cs.bak
57+
src/flagd/demo.flagd.json.bak

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ the release.
3030
([#2737](https://github.com/open-telemetry/opentelemetry-demo/pull/2737))
3131
* [collector] [dockerstats/receiver] Set API version to 1.44
3232
([#2767](https://github.com/open-telemetry/opentelemetry-demo/pull/2767))
33+
* [cart] Add health check endpoint
34+
([#2830](https://github.com/open-telemetry/opentelemetry-demo/pull/2830))
3335
* [product-catalog] Use Postgres database for products
3436
([#2859](https://github.com/open-telemetry/opentelemetry-demo/pull/2859))
3537

src/cart/src/Program.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@
22
// SPDX-License-Identifier: Apache-2.0
33
using System;
44

5+
using Grpc.Health.V1;
6+
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
7+
using System.Threading.Tasks;
8+
using System.Threading;
9+
10+
using Grpc.Core;
11+
512
using cart.cartstore;
613
using cart.services;
14+
using cart.healthcheck;
715

816
using Microsoft.AspNetCore.Builder;
917
using Microsoft.AspNetCore.Http;
@@ -79,20 +87,25 @@
7987
.SetExemplarFilter(ExemplarFilterType.TraceBased)
8088
.AddOtlpExporter());
8189
builder.Services.AddGrpc();
90+
builder.Services.AddSingleton<readinessCheck>();
8291
builder.Services.AddGrpcHealthChecks()
83-
.AddCheck("Sample", () => HealthCheckResult.Healthy());
92+
.AddCheck<readinessCheck>("oteldemo.CartService");
93+
94+
builder.Services.AddSingleton<HealthServiceImpl>();
8495

8596
var app = builder.Build();
8697

8798
var ValkeyCartStore = (ValkeyCartStore)app.Services.GetRequiredService<ICartStore>();
8899
app.Services.GetRequiredService<StackExchangeRedisInstrumentation>().AddConnection(ValkeyCartStore.GetConnection());
89100

90101
app.MapGrpcService<CartService>();
91-
app.MapGrpcHealthChecksService();
102+
app.MapGrpcService<HealthServiceImpl>();
92103

93104
app.MapGet("/", async context =>
94105
{
95106
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
96107
});
97108

98109
app.Run();
110+
111+
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
6+
using Grpc.Core;
7+
using Grpc.HealthCheck;
8+
using Grpc.Health.V1;
9+
using System.Threading.Tasks;
10+
using System.Threading;
11+
12+
using OpenFeature;
13+
using OpenFeature.Hooks;
14+
using OpenFeature.Contrib.Providers.Flagd;
15+
16+
using Microsoft.Extensions.DependencyInjection;
17+
using Microsoft.Extensions.Diagnostics.HealthChecks;
18+
using Microsoft.Extensions.Logging;
19+
20+
namespace cart.healthcheck
21+
{
22+
public class readinessCheck : IHealthCheck
23+
{
24+
private readonly IFeatureClient _featureClient;
25+
26+
public readinessCheck(IFeatureClient featureClient)
27+
{
28+
_featureClient = featureClient;
29+
}
30+
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
31+
{
32+
33+
#pragma warning disable CA2016 // OpenFeature does not support CancellationToken
34+
// Await the async call instead of blocking
35+
bool isSet = await _featureClient.GetBooleanValueAsync("failedReadinessProbe", false); // Replace with actual check
36+
#pragma warning restore CA2016
37+
if (isSet)
38+
{
39+
return HealthCheckResult.Unhealthy("connection failed");
40+
41+
}
42+
43+
return HealthCheckResult.Healthy("healthy");
44+
}
45+
}
46+
47+
48+
public class HealthServiceImpl : Health.HealthBase
49+
{
50+
private readonly ILogger<HealthServiceImpl> _logger;
51+
private readonly HealthCheckService _healthCheckService;
52+
53+
54+
public HealthServiceImpl(
55+
ILogger<HealthServiceImpl> logger,
56+
HealthCheckService healthCheckService)
57+
{
58+
_logger = logger;
59+
_healthCheckService = healthCheckService;
60+
}
61+
62+
public override async Task<HealthCheckResponse> Check(HealthCheckRequest request, ServerCallContext context)
63+
{
64+
if (_logger.IsEnabled(LogLevel.Information))
65+
{
66+
_logger.LogInformation("Received health check request for service: {Service}", request.Service);
67+
}
68+
var cancellationToken = context.CancellationToken;
69+
// If service is empty or null, check overall health
70+
if (string.IsNullOrEmpty(request.Service))
71+
{
72+
var health = await _healthCheckService.CheckHealthAsync(cancellationToken);
73+
return new HealthCheckResponse
74+
{
75+
Status = ConvertToGrpcStatus(health.Status)
76+
};
77+
}
78+
79+
// You can implement service-specific health checks here
80+
// This example checks a specific service
81+
var serviceHealth = await _healthCheckService.CheckHealthAsync(registration => MatchesService(registration, request.Service), cancellationToken);
82+
return new HealthCheckResponse
83+
{
84+
Status = ConvertToGrpcStatus(serviceHealth.Entries[request.Service].Status)
85+
};
86+
}
87+
88+
private static bool MatchesService(HealthCheckRegistration registration, string service)
89+
{
90+
return registration.Name == service;
91+
}
92+
93+
public override async Task Watch(HealthCheckRequest request, IServerStreamWriter<HealthCheckResponse> responseStream, ServerCallContext context)
94+
{
95+
if (_logger.IsEnabled(LogLevel.Information))
96+
{
97+
_logger.LogInformation("Received health watch request for service: {Service}", request.Service);
98+
}
99+
// Simple implementation to send current status once
100+
var response = await Check(request, context);
101+
await responseStream.WriteAsync(response);
102+
103+
// In a real implementation, you would periodically check health and send updates
104+
// This might involve setting up a timer or listener for health changes
105+
}
106+
107+
private static HealthCheckResponse.Types.ServingStatus ConvertToGrpcStatus(HealthStatus status)
108+
{
109+
return status switch
110+
{
111+
HealthStatus.Healthy => HealthCheckResponse.Types.ServingStatus.Serving,
112+
HealthStatus.Degraded => HealthCheckResponse.Types.ServingStatus.Serving, // Or you might want to use SERVING_WITH_ISSUES if available
113+
HealthStatus.Unhealthy => HealthCheckResponse.Types.ServingStatus.NotServing,
114+
_ => HealthCheckResponse.Types.ServingStatus.Unknown
115+
};
116+
}
117+
}
118+
}

src/flagd/demo.flagd.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@
124124
},
125125
"defaultVariant": "off"
126126
},
127+
"failedReadinessProbe": {
128+
"description": "readiness probe failure for cart service",
129+
"state": "ENABLED",
130+
"variants": {
131+
"on": true,
132+
"off": false
133+
},
134+
"defaultVariant": "off"
135+
136+
},
127137
"emailMemoryLeak": {
128138
"description": "Memory leak in the email service.",
129139
"state": "ENABLED",

0 commit comments

Comments
 (0)