diff --git a/README.md b/README.md index fb020cf6..a63efd9f 100644 --- a/README.md +++ b/README.md @@ -840,6 +840,28 @@ various general statistics. } ``` +In clustering mode, there is an additional field that indicates the RAFT role of the given node. +Here is an example: +``` +{ + "cluster_id": "test-cluster", + "server_id": "5bJdRWJW4dSxrfjKSUOgOH", + "version": "0.12.2", + "go": "go1.12.1", + "state": "CLUSTERED", + "role": "Leader", + "now": "2019-04-15T19:25:39.350491-06:00", + "start_time": "2019-04-15T19:25:30.75881-06:00", + "uptime": "8s", + "clients": 0, + "subscriptions": 0, + "channels": 0, + "total_msgs": 0, + "total_bytes": 0 +} +``` +The possible values are: `Leader`, `Follower` or `Candidate`. + #### /storez The endpoint [http://localhost:8222/streaming/storez](http://localhost:8222/streaming/storez) reports diff --git a/server/monitor.go b/server/monitor.go index 1b17b66b..97e69478 100644 --- a/server/monitor.go +++ b/server/monitor.go @@ -47,6 +47,7 @@ type Serverz struct { Version string `json:"version"` GoVersion string `json:"go"` State string `json:"state"` + Role string `json:"role,omitempty"` Now time.Time `json:"now"` Start time.Time `json:"start_time"` Uptime string `json:"uptime"` @@ -184,8 +185,12 @@ func (s *StanServer) handleServerz(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Error getting information about channels state: %v", err), http.StatusInternalServerError) return } + var role string s.mu.RLock() state := s.state + if s.raft != nil { + role = s.raft.State().String() + } s.mu.RUnlock() s.monMu.RLock() numSubs := s.numSubs @@ -214,6 +219,7 @@ func (s *StanServer) handleServerz(w http.ResponseWriter, r *http.Request) { Version: VERSION, GoVersion: runtime.Version(), State: state.String(), + Role: role, Now: now, Start: s.startTime, Uptime: myUptime(now.Sub(s.startTime)), diff --git a/server/monitor_test.go b/server/monitor_test.go index 2bf68a42..16520d96 100644 --- a/server/monitor_test.go +++ b/server/monitor_test.go @@ -27,6 +27,7 @@ import ( "testing" "time" + "github.com/hashicorp/raft" natsd "github.com/nats-io/gnatsd/server" natsdTest "github.com/nats-io/gnatsd/test" "github.com/nats-io/go-nats" @@ -1159,3 +1160,59 @@ func TestMonitorDurableSubs(t *testing.T) { } } } + +func TestMonitorClusterRole(t *testing.T) { + nOpts := defaultMonitorOptions + for _, test := range []struct { + name string + expectedRole string + n1Opts *natsd.Options + n2Opts *natsd.Options + }{ + { + "leader", + raft.Leader.String(), + &nOpts, + nil, + }, + { + "follower", + raft.Follower.String(), + nil, + &nOpts, + }, + } { + t.Run(test.name, func(t *testing.T) { + resetPreviousHTTPConnections() + + cleanupDatastore(t) + defer cleanupDatastore(t) + cleanupRaftLog(t) + defer cleanupRaftLog(t) + + // For this test, use a central NATS server. + ns := natsdTest.RunDefaultServer() + defer ns.Shutdown() + + s1sOpts := getTestDefaultOptsForClustering("a", true) + s1 := runServerWithOpts(t, s1sOpts, test.n1Opts) + defer s1.Shutdown() + + s2sOpts := getTestDefaultOptsForClustering("b", false) + s2 := runServerWithOpts(t, s2sOpts, test.n2Opts) + defer s2.Shutdown() + + getLeader(t, 10*time.Second, s1, s2) + + resp, body := getBody(t, ServerPath, expectedJSON) + resp.Body.Close() + sz := Serverz{} + if err := json.Unmarshal(body, &sz); err != nil { + t.Fatalf("Got an error unmarshalling the body: %v", err) + } + if sz.Role != test.expectedRole { + t.Fatalf("Expected role to be %v, gt %v", test.expectedRole, sz.Role) + } + }) + } +}