diff --git a/CHANGELOG.md b/CHANGELOG.md index ab902f9e..09342c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ -## 8.21.2 (February 23rd 2024) +## 8.21.3 (unreleased) ### Implementation changes and bug fixes -- [PR #1697](https://github.com/rqlite/rqlite/pull/1697): Use consistent file perms post Boot. +- [PR #1700](https://github.com/rqlite/rqlite/pull/1700): Accessing non-open Store shouldn't panic. Fixes issue [#1698](https://github.com/rqlite/rqlite/issues/1698). + ## 8.21.1 (February 21st 2024) ### Implementation changes and bug fixes diff --git a/store/store.go b/store/store.go index db4035da..d0f0fb43 100644 --- a/store/store.go +++ b/store/store.go @@ -591,6 +591,9 @@ func (s *Store) Bootstrap(servers ...*Server) error { // the cluster. If this node is not the leader, and 'wait' is true, an error // will be returned. func (s *Store) Stepdown(wait bool) error { + if !s.open { + return ErrNotOpen + } f := s.raft.LeadershipTransfer() if !wait { return nil @@ -760,11 +763,17 @@ func (s *Store) DBAppliedIndex() uint64 { // IsLeader is used to determine if the current node is cluster leader func (s *Store) IsLeader() bool { + if !s.open { + return false + } return s.raft.State() == raft.Leader } // HasLeader returns true if the cluster has a leader, false otherwise. func (s *Store) HasLeader() bool { + if !s.open { + return false + } return s.raft.Leader() != "" } @@ -772,6 +781,9 @@ func (s *Store) HasLeader() bool { // is no reference to the current node in the current cluster configuration then // false will also be returned. func (s *Store) IsVoter() (bool, error) { + if !s.open { + return false, ErrNotOpen + } cfg := s.raft.GetConfiguration() if err := cfg.Error(); err != nil { return false, err @@ -786,6 +798,9 @@ func (s *Store) IsVoter() (bool, error) { // State returns the current node's Raft state func (s *Store) State() ClusterState { + if !s.open { + return Unknown + } state := s.raft.State() switch state { case raft.Leader: @@ -856,6 +871,9 @@ func (s *Store) LeaderWithID() (string, string) { // CommitIndex returns the Raft commit index. func (s *Store) CommitIndex() (uint64, error) { + if !s.open { + return 0, ErrNotOpen + } return s.raft.CommitIndex(), nil } @@ -863,6 +881,9 @@ func (s *Store) CommitIndex() (uint64, error) { // by the latest AppendEntries RPC. If this node is the Leader then the // commit index is returned directly from the Raft object. func (s *Store) LeaderCommitIndex() (uint64, error) { + if !s.open { + return 0, ErrNotOpen + } if s.raft.State() == raft.Leader { return s.raft.CommitIndex(), nil } diff --git a/store/store_test.go b/store/store_test.go index c4256fae..1e3bac5f 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -22,6 +22,47 @@ import ( "github.com/rqlite/rqlite/v8/testdata/chinook" ) +// Test_StoreSingleNode tests that a non-open Store handles public methods correctly. +func Test_NonOpenStore(t *testing.T) { + s, ln := mustNewStore(t) + defer s.Close(true) + defer ln.Close() + + if err := s.Stepdown(false); err != ErrNotOpen { + t.Fatalf("wrong error received for non-open store: %s", err) + } + if s.IsLeader() { + t.Fatalf("store incorrectly marked as leader") + } + if s.HasLeader() { + t.Fatalf("store incorrectly marked as having leader") + } + if _, err := s.IsVoter(); err != ErrNotOpen { + t.Fatalf("wrong error received for non-open store: %s", err) + } + if s.State() != Unknown { + t.Fatalf("wrong cluster state returned for non-open store") + } + if _, err := s.CommitIndex(); err != ErrNotOpen { + t.Fatalf("wrong error received for non-open store: %s", err) + } + if _, err := s.LeaderCommitIndex(); err != ErrNotOpen { + t.Fatalf("wrong error received for non-open store: %s", err) + } + if addr, err := s.LeaderAddr(); addr != "" || err != nil { + t.Fatalf("wrong leader address returned for non-open store: %s", addr) + } + if id, err := s.LeaderID(); id != "" || err != nil { + t.Fatalf("wrong leader ID returned for non-open store: %s", id) + } + if addr, id := s.LeaderWithID(); addr != "" || id != "" { + t.Fatalf("wrong leader address and ID returned for non-open store: %s", id) + } + if _, err := s.Nodes(); err != ErrNotOpen { + t.Fatalf("wrong error received for non-open store: %s", err) + } +} + // Test_StoreSingleNode tests that a single node basically operates. func Test_OpenStoreSingleNode(t *testing.T) { s, ln := mustNewStore(t)