From 1634f33de7a298c68c946775e47c42125a05942d Mon Sep 17 00:00:00 2001 From: Muhammad Faizan Date: Wed, 3 Aug 2022 10:25:50 +0200 Subject: [PATCH] Added param options to /healthz endpoint --- server/events.go | 10 +++++++-- server/events_test.go | 2 ++ server/jetstream_helpers_test.go | 4 ++-- server/jetstream_test.go | 20 +++++++++++++++-- server/monitor.go | 38 ++++++++++++++++++++++++++++++-- 5 files changed, 66 insertions(+), 8 deletions(-) diff --git a/server/events.go b/server/events.go index 441434d6..f9fb64c7 100644 --- a/server/events.go +++ b/server/events.go @@ -875,8 +875,8 @@ func (s *Server) initEventTracking() { s.zReq(c, reply, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.Jsz(&optz.JSzOptions) }) }, "HEALTHZ": func(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - optz := &EventFilterOptions{} - s.zReq(c, reply, msg, optz, optz, func() (interface{}, error) { return s.healthz(), nil }) + optz := &HealthzEventOptions{} + s.zReq(c, reply, msg, &optz.EventFilterOptions, optz, func() (interface{}, error) { return s.healthz(&optz.HealthzOptions), nil }) }, } for name, req := range monSrvc { @@ -1433,6 +1433,12 @@ type JszEventOptions struct { EventFilterOptions } +// In the context of system events, HealthzEventOptions are options passed to Healthz +type HealthzEventOptions struct { + HealthzOptions + EventFilterOptions +} + // returns true if the request does NOT apply to this server and can be ignored. // DO NOT hold the server lock when func (s *Server) filterRequest(fOpts *EventFilterOptions) bool { diff --git a/server/events_test.go b/server/events_test.go index 5c14f94b..0056d697 100644 --- a/server/events_test.go +++ b/server/events_test.go @@ -2170,6 +2170,8 @@ func TestServerEventsPingMonitorz(t *testing.T) { {"JSZ", nil, &JSzOptions{}, []string{"now", "disabled"}}, {"HEALTHZ", nil, &JSzOptions{}, []string{"status"}}, + {"HEALTHZ", &HealthzOptions{JSEnabled: true}, &JSzOptions{}, []string{"status"}}, + {"HEALTHZ", &HealthzOptions{JSServerOnly: true}, &JSzOptions{}, []string{"status"}}, } for i, test := range tests { diff --git a/server/jetstream_helpers_test.go b/server/jetstream_helpers_test.go index 325aa698..f633ef34 100644 --- a/server/jetstream_helpers_test.go +++ b/server/jetstream_helpers_test.go @@ -1194,13 +1194,13 @@ func (c *cluster) waitOnServerHealthz(s *Server) { c.t.Helper() expires := time.Now().Add(30 * time.Second) for time.Now().Before(expires) { - hs := s.healthz() + hs := s.healthz(nil) if hs.Status == "ok" && hs.Error == _EMPTY_ { return } time.Sleep(100 * time.Millisecond) } - c.t.Fatalf("Expected server %q to eventually return healthz 'ok', but got %q", s, s.healthz().Error) + c.t.Fatalf("Expected server %q to eventually return healthz 'ok', but got %q", s, s.healthz(nil).Error) } func (c *cluster) waitOnServerCurrent(s *Server) { diff --git a/server/jetstream_test.go b/server/jetstream_test.go index da7da323..b4bcd71d 100644 --- a/server/jetstream_test.go +++ b/server/jetstream_test.go @@ -17369,7 +17369,7 @@ func TestJetStreamWorkQueueSourceRestart(t *testing.T) { defer s.Shutdown() checkFor(t, 10*time.Second, 200*time.Millisecond, func() error { - hs := s.healthz() + hs := s.healthz(nil) if hs.Status == "ok" && hs.Error == _EMPTY_ { return nil } @@ -17490,7 +17490,7 @@ func TestJetStreamWorkQueueSourceNamingRestart(t *testing.T) { defer s.Shutdown() checkFor(t, 10*time.Second, 200*time.Millisecond, func() error { - hs := s.healthz() + hs := s.healthz(nil) if hs.Status == "ok" && hs.Error == _EMPTY_ { return nil } @@ -17508,6 +17508,22 @@ func TestJetStreamWorkQueueSourceNamingRestart(t *testing.T) { } } +func TestJetStreamDisabledHealthz(t *testing.T) { + s := RunRandClientPortServer() + defer s.Shutdown() + + if s.JetStreamEnabled() { + t.Fatalf("Expected JetStream to be disabled") + } + + hs := s.healthz(&HealthzOptions{JSEnabled: true}) + if hs.Status == "unavailable" && hs.Error == NewJSNotEnabledError().Error() { + return + } + + t.Fatalf("Expected healthz to return error if JetStream is disabled, got status: %s", hs.Status) +} + func TestJetStreamPullMaxBytes(t *testing.T) { s := RunBasicJetStreamServer() if config := s.JetStreamConfig(); config != nil { diff --git a/server/monitor.go b/server/monitor.go index 8c0b0b58..9e9f6a9c 100644 --- a/server/monitor.go +++ b/server/monitor.go @@ -2603,6 +2603,12 @@ type JSzOptions struct { Limit int `json:"limit,omitempty"` } +// HealthzOptions are options passed to Healthz +type HealthzOptions struct { + JSEnabled bool `json:"js-enabled,omitempty"` + JSServerOnly bool `json:"js-server-only,omitempty"` +} + type StreamDetail struct { Name string `json:"name"` Cluster *ClusterInfo `json:"cluster,omitempty"` @@ -2928,7 +2934,19 @@ func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request) { s.httpReqStats[HealthzPath]++ s.mu.Unlock() - hs := s.healthz() + jsEnabled, err := decodeBool(w, r, "js-enabled") + if err != nil { + return + } + jsServerOnly, err := decodeBool(w, r, "js-server-only") + if err != nil { + return + } + + hs := s.healthz(&HealthzOptions{ + JSEnabled: jsEnabled, + JSServerOnly: jsServerOnly, + }) if hs.Error != _EMPTY_ { s.Warnf("Healthcheck failed: %q", hs.Error) w.WriteHeader(http.StatusServiceUnavailable) @@ -2942,9 +2960,14 @@ func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request) { } // Generate health status. -func (s *Server) healthz() *HealthStatus { +func (s *Server) healthz(opts *HealthzOptions) *HealthStatus { var health = &HealthStatus{Status: "ok"} + // set option defaults + if opts == nil { + opts = &HealthzOptions{} + } + if err := s.readyForConnections(time.Millisecond); err != nil { health.Status = "error" health.Error = err.Error() @@ -2954,6 +2977,12 @@ func (s *Server) healthz() *HealthStatus { // Check JetStream js := s.getJetStream() if js == nil { + // If JetStream should be enabled then return error status. + if opts.JSEnabled { + health.Status = "unavailable" + health.Error = NewJSNotEnabledError().Error() + return health + } return health } @@ -2985,6 +3014,11 @@ func (s *Server) healthz() *HealthStatus { return health } + // If JSServerOnly is true, then do not check further accounts, streams and consumers. + if opts.JSServerOnly { + return health + } + // Range across all accounts, the streams assigned to them, and the consumers. // If they are assigned to this server check their status. for acc, asa := range cc.streams {