Skip to content

Commit 29ea0e5

Browse files
author
srahul3
committed
Add session health check management and tests
1 parent 7665dd3 commit 29ea0e5

File tree

9 files changed

+265
-110
lines changed

9 files changed

+265
-110
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 1.21.0 (March 17, 2025)
2+
* Enhancement: Added support for Consul Session to update the state of a Health Check, allowing for more dynamic and responsive health monitoring within the Consul ecosystem. This feature enables sessions to directly influence health check statuses, improving the overall reliability and accuracy of service health assessments.
3+
14
## 1.20.3 (February 13, 2025)
25

36
SECURITY:

agent/consul/state/session.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package state
55

66
import (
77
"fmt"
8+
"github.com/hashicorp/consul/api"
89
"reflect"
910
"strings"
1011
"time"
@@ -448,5 +449,28 @@ func (s *Store) deleteSessionTxn(tx WriteTxn, idx uint64, sessionID string, entM
448449
}
449450
}
450451

452+
// session invalidating the health-checks
453+
return s.markSessionCheckCritical(tx, idx, session)
454+
}
455+
456+
// markSessionCheckCritical The method marks the health-checks associated with the session as critical
457+
func (s *Store) markSessionCheckCritical(tx WriteTxn, idx uint64, session *structs.Session) error {
458+
// Find all checks for the given Node
459+
iter, err := tx.Get(tableChecks, indexNode, Query{Value: session.Node, EnterpriseMeta: session.EnterpriseMeta})
460+
if err != nil {
461+
return fmt.Errorf("failed check lookup: %s", err)
462+
}
463+
464+
for check := iter.Next(); check != nil; check = iter.Next() {
465+
if hc := check.(*structs.HealthCheck); hc.Type == "session" && hc.Definition.SessionName == session.Name {
466+
updatedCheck := hc.Clone()
467+
updatedCheck.Status = api.HealthCritical
468+
updatedCheck.Output = fmt.Sprintf("Session '%s' is invalid", session.Name)
469+
470+
if err := s.ensureCheckTxn(tx, idx, true, updatedCheck); err != nil {
471+
return err
472+
}
473+
}
474+
}
451475
return nil
452476
}

agent/consul/state/session_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,3 +961,74 @@ func TestStateStore_Session_Invalidate_PreparedQuery_Delete(t *testing.T) {
961961
t.Fatalf("bad: %v", q2)
962962
}
963963
}
964+
965+
func TestHealthCheck_SessionDestroy(t *testing.T) {
966+
s := testStateStore(t)
967+
968+
var check *structs.HealthCheck
969+
// setup node
970+
testRegisterNode(t, s, 1, "foo-node")
971+
testRegisterCheckCustom(t, s, 1, "foo", func(chk *structs.HealthCheck) {
972+
chk.Node = "foo-node"
973+
chk.Type = "session"
974+
chk.Status = api.HealthPassing
975+
chk.Definition = structs.HealthCheckDefinition{
976+
SessionName: "test-session",
977+
}
978+
check = chk
979+
})
980+
981+
// Ensure the index was not updated if nothing was destroyed.
982+
if idx := s.maxIndex("sessions"); idx != 0 {
983+
t.Fatalf("bad index: %d", idx)
984+
}
985+
986+
// Register a new session
987+
sess := &structs.Session{
988+
ID: testUUID(),
989+
Node: "foo-node",
990+
Name: "test-session",
991+
}
992+
if err := s.SessionCreate(2, sess); err != nil {
993+
t.Fatalf("err: %s", err)
994+
}
995+
996+
_, hc, err := s.ChecksInState(nil, api.HealthAny, structs.DefaultEnterpriseMetaInPartition(""), structs.DefaultPeerKeyword)
997+
if err != nil {
998+
t.Fatalf("err: %s", err)
999+
}
1000+
// iterate over checks and find the right one
1001+
found := false
1002+
for _, c := range hc {
1003+
if c.CheckID == check.CheckID && c.Status == api.HealthPassing {
1004+
found = true
1005+
break
1006+
}
1007+
}
1008+
1009+
if !found {
1010+
t.Fatalf("check is expected to be passing")
1011+
}
1012+
1013+
// Destroy the session.
1014+
if err := s.SessionDestroy(3, sess.ID, nil); err != nil {
1015+
t.Fatalf("err: %s", err)
1016+
}
1017+
1018+
_, hc, err = s.ChecksInState(nil, api.HealthAny, structs.DefaultEnterpriseMetaInPartition(""), structs.DefaultPeerKeyword)
1019+
if err != nil {
1020+
t.Fatalf("err: %s", err)
1021+
}
1022+
// iterate over checks and find the right one
1023+
found = false
1024+
for _, c := range hc {
1025+
if c.CheckID == check.CheckID && c.Status == api.HealthCritical {
1026+
found = true
1027+
break
1028+
}
1029+
}
1030+
1031+
if !found {
1032+
t.Fatalf("check is expected to be critical")
1033+
}
1034+
}

agent/consul/state/state_store_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,29 @@ func testRegisterCheckWithPartition(t *testing.T, s *Store, idx uint64,
308308
}
309309
}
310310

311+
func testRegisterCheckCustom(t *testing.T, s *Store, idx uint64, checkID types.CheckID, update func(chk *structs.HealthCheck)) {
312+
chk := &structs.HealthCheck{
313+
CheckID: checkID,
314+
EnterpriseMeta: *structs.DefaultEnterpriseMetaInPartition(""),
315+
}
316+
317+
update(chk)
318+
319+
if err := s.EnsureCheck(idx, chk); err != nil {
320+
t.Fatalf("err: %s", err)
321+
}
322+
323+
tx := s.db.Txn(false)
324+
defer tx.Abort()
325+
c, err := tx.First(tableChecks, indexID, NodeCheckQuery{Node: chk.Node, CheckID: string(checkID), EnterpriseMeta: chk.EnterpriseMeta})
326+
if err != nil {
327+
t.Fatalf("err: %s", err)
328+
}
329+
if result, ok := c.(*structs.HealthCheck); !ok || result.CheckID != checkID {
330+
t.Fatalf("bad check: %#v", result)
331+
}
332+
}
333+
311334
func testRegisterSidecarProxy(t *testing.T, s *Store, idx uint64, nodeID string, targetServiceID string) {
312335
testRegisterSidecarProxyOpts(t, s, idx, nodeID, targetServiceID)
313336
}

agent/structs/structs.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,6 +1912,7 @@ type HealthCheckDefinition struct {
19121912
GRPCUseTLS bool `json:",omitempty"`
19131913
AliasNode string `json:",omitempty"`
19141914
AliasService string `json:",omitempty"`
1915+
SessionName string `json:",omitempty"`
19151916
TTL time.Duration `json:",omitempty"`
19161917
}
19171918

api/health.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ type HealthCheckDefinition struct {
7575
IntervalDuration time.Duration `json:"-"`
7676
TimeoutDuration time.Duration `json:"-"`
7777
DeregisterCriticalServiceAfterDuration time.Duration `json:"-"`
78+
// when parent Type is `session`, and if this session is destroyed, the check will be marked as critical
79+
SessionName string `json:",omitempty"`
7880

7981
// DEPRECATED in Consul 1.4.1. Use the above time.Duration fields instead.
8082
Interval ReadableDuration

proto/private/pbservice/healthcheck.gen.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)