Skip to content

Commit 665ba23

Browse files
BCDA-9633: add worker health check (#1276)
## 🎫 Ticket https://jira.cms.gov/browse/BCDA-9633 Related tickets: [CDAP](CMSgov/cdap#354) [bcda-ops](CMSgov/bcda-ops#1303) ## 🛠 Changes <!-- What was added, updated, or removed in this PR? --> - added local health checks for ssas and api, with an additional dockerfile dependency on curl - added local health check for worker - refactored worker into a command-line app, with two entrypoints: `start-worker` and `health` - added an interface for HealthChecker and a mock implementation with Mockery - added a makefile target for generating mocks with Mockery that pins a shared Mockery version and enables devs to not have to install Mockery locally ## ℹ️ Context <!-- Why were these changes made? Add background context suitable for a non-technical audience. --> Since the worker service doesn't have a target group or load balancer, it would be useful to have a container-level health check that monitors the health of the service. The worker does not have any api endpoints like API and SSAS, so a command-line interface was added to enable easy verification of the worker health. This app has two commands: `start-worker` (which starts the worker service) and `health` (which performs the same health check that is run on a timed loop currently in the worker). For temporary compatibility with EC2, this health check can be configured to still run in a timed loop, but this is skipped for ECS. Sonarqube will complain about the test coverage -- I tested what I thought was reasonable, but much of the gap is the CLI entrypoint that I don't believe is relevant (and is untested in its current form on main). <!-- If any of the following security implications apply, this PR must not be merged without Stephen Walter's approval. Explain in this section and add @SJWalter11 as a reviewer. - Adds a new software dependency or dependencies. - Modifies or invalidates one or more of our security controls. - Stores or transmits data that was not stored or transmitted before. - Requires additional review of security implications for other reasons. --> ## 🧪 Validation <!-- How were the changes verified? Did you fully test the acceptance criteria in the ticket? Provide reproducible testing instructions and screenshots if applicable. --> Tested in dev on ECS and EC2
1 parent bc7b883 commit 665ba23

File tree

13 files changed

+589
-151
lines changed

13 files changed

+589
-151
lines changed

.github/workflows/dev-daily-autodeploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
name: Deploy main to dev daily
33

44
on:
5+
workflow_dispatch:
56
schedule:
67
- cron: 0 12 * * 1-5 # every workday at 8am EST
78

.mockery.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ packages:
1414
github.com/CMSgov/bcda-app/bcda/auth:
1515
interfaces:
1616
Provider:
17+
github.com/CMSgov/bcda-app/bcda/health:
18+
interfaces:
19+
HealthChecker:
1720
github.com/CMSgov/bcda-app/bcda/models:
1821
interfaces:
1922
Repository:

Dockerfiles/Dockerfile.bcda

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ FROM golang:1.25.4-alpine3.22
2525

2626
RUN addgroup -S -g 1200 bcda && adduser -S -G bcda -u 1100 bcda && \
2727
apk update upgrade --no-cache && \
28-
apk add --no-cache aws-cli
28+
apk add --no-cache \
29+
aws-cli \
30+
curl
2931

3032
# install dev packages if the environment argument was set to development
3133
ARG ENVIRONMENT

Dockerfiles/Dockerfile.bcdaworker

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ USER bcda
5555
ENV APP_NAME="worker"
5656

5757
ENTRYPOINT ["./entrypoint.sh"]
58-
CMD ["bcdaworker"]
58+
CMD ["bcdaworker", "start-worker"]

Dockerfiles/Dockerfile.ssas

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ RUN --mount=type=cache,target=/go/pkg/mod \
2828

2929
FROM golang:1.25.4-alpine3.22
3030

31-
RUN --mount=type=cache,target=/var/cache/apk \
32-
apk update upgrade && \
33-
apk add openssl
31+
RUN apk update upgrade && \
32+
apk add --no-cache \
33+
curl \
34+
openssl
3435

3536
RUN openssl genrsa -out /var/local/private.pem 2048
3637
RUN openssl rsa -in /var/local/private.pem -outform PEM -pubout -out /var/local/public.pem

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,10 @@ fhir_testing:
177177
-e CLIENT_SECRET='${CLIENT_SECRET}' \
178178
fhir_testing
179179

180-
.PHONY: api-shell debug-api debug-worker docker-bootstrap docker-build lint load-fixtures load-fixtures-ssas package performance-test postman release smoke-test test unit-test worker-shell bdt fhir_testing unit-test-db unit-test-db-snapshot reset-db dbdocs
180+
generate-mocks:
181+
docker run -v "$PWD":/src -w /src vektra/mockery:v3.6.1
182+
183+
.PHONY: api-shell debug-api debug-worker docker-bootstrap docker-build generate-mocks lint load-fixtures load-fixtures-ssas package performance-test postman release smoke-test test unit-test worker-shell bdt fhir_testing unit-test-db unit-test-db-snapshot reset-db dbdocs
181184

182185
credentials:
183186
$(eval ACO_CMS_ID = A9994)

bcda/health/health.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@ type introspectCache struct {
2323
mu sync.RWMutex
2424
}
2525

26-
type HealthChecker struct {
26+
type HealthChecker interface {
27+
IsDatabaseOK() (string, bool)
28+
IsWorkerDatabaseOK() (string, bool)
29+
IsBlueButtonOK() bool
30+
IsSsasOK() (string, bool)
31+
IsSsasIntrospectOK() (string, bool)
32+
}
33+
34+
type healthCheck struct {
2735
db *sql.DB
2836
introspectCache *introspectCache
2937
}
@@ -35,13 +43,13 @@ const (
3543
)
3644

3745
func NewHealthChecker(db *sql.DB) HealthChecker {
38-
return HealthChecker{
46+
return healthCheck{
3947
db: db,
4048
introspectCache: &introspectCache{},
4149
}
4250
}
4351

44-
func (h HealthChecker) IsDatabaseOK() (result string, ok bool) {
52+
func (h healthCheck) IsDatabaseOK() (result string, ok bool) {
4553
if err := h.db.Ping(); err != nil {
4654
log.API.Error("Health check: database ping error: ", err.Error())
4755
return "database ping error", false
@@ -50,7 +58,7 @@ func (h HealthChecker) IsDatabaseOK() (result string, ok bool) {
5058
return "ok", true
5159
}
5260

53-
func (h HealthChecker) IsWorkerDatabaseOK() (result string, ok bool) {
61+
func (h healthCheck) IsWorkerDatabaseOK() (result string, ok bool) {
5462
if err := h.db.Ping(); err != nil {
5563
log.Worker.Error("Health check: database ping error: ", err.Error())
5664
return "database ping error", false
@@ -59,7 +67,7 @@ func (h HealthChecker) IsWorkerDatabaseOK() (result string, ok bool) {
5967
return "ok", true
6068
}
6169

62-
func (h HealthChecker) IsBlueButtonOK() bool {
70+
func (h healthCheck) IsBlueButtonOK() bool {
6371
bbc, err := client.NewBlueButtonClient(client.NewConfig("/v1/fhir"))
6472
if err != nil {
6573
log.Worker.Error("Health check: Blue Button client error: ", err.Error())
@@ -75,7 +83,7 @@ func (h HealthChecker) IsBlueButtonOK() bool {
7583
return true
7684
}
7785

78-
func (h HealthChecker) IsSsasOK() (result string, ok bool) {
86+
func (h healthCheck) IsSsasOK() (result string, ok bool) {
7987
c, err := ssasClient.NewSSASClient()
8088
if err != nil {
8189
log.Auth.Errorf("no client for SSAS. no provider set; %s", err.Error())
@@ -88,7 +96,7 @@ func (h HealthChecker) IsSsasOK() (result string, ok bool) {
8896
return "ok", true
8997
}
9098

91-
func (h HealthChecker) IsSsasIntrospectOK() (result string, ok bool) {
99+
func (h healthCheck) IsSsasIntrospectOK() (result string, ok bool) {
92100
// Check cache first
93101
h.introspectCache.mu.RLock()
94102
if h.introspectCache.timestamp.Add(introspectCacheTTL).After(time.Now()) {
@@ -185,7 +193,7 @@ func isRetryableError(err error) bool {
185193
}
186194

187195
// introspectWithRetry attempts to call introspect with exponential backoff retry
188-
func (h HealthChecker) introspectWithRetry(c *ssasClient.SSASClient, ctx context.Context, tokenString string) ([]byte, error) {
196+
func (h healthCheck) introspectWithRetry(c *ssasClient.SSASClient, ctx context.Context, tokenString string) ([]byte, error) {
189197
eb := backoff.NewExponentialBackOff()
190198
eb.InitialInterval = introspectRetryInitial
191199
eb.MaxInterval = 2 * time.Second

bcda/health/health_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ var (
2727

2828
type HealthCheckerTestSuite struct {
2929
suite.Suite
30-
hc HealthChecker
30+
hc healthCheck
3131
}
3232

3333
func (s *HealthCheckerTestSuite) SetupSuite() {
@@ -39,7 +39,10 @@ func (s *HealthCheckerTestSuite) SetupSuite() {
3939
}
4040

4141
func (s *HealthCheckerTestSuite) SetupTest() {
42-
s.hc = NewHealthChecker(nil)
42+
s.hc = healthCheck{
43+
db: nil,
44+
introspectCache: &introspectCache{},
45+
}
4346
}
4447

4548
func (s *HealthCheckerTestSuite) TearDownTest() {

0 commit comments

Comments
 (0)