diff --git a/CHANGELOG.md b/CHANGELOG.md index ee6faa8d..35c23eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 2.2.3 (unreleased) +- [PR #104](https://github.com/otoolep/rqlite/pull/104): Handle the `-join` option sensibly when already member of cluster. + ## 2.2.2 (April 24th 2016) - [PR #96](https://github.com/otoolep/rqlite/pull/96): Add build time to status output. - [PR #101](https://github.com/otoolep/rqlite/pull/101): Fix restore to in-memory databases. diff --git a/README.md b/README.md index 7d672d7e..c2210e5a 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,7 @@ rqlited -http localhost:4005 -raft :4006 -join http://localhost:4001 ~/node.3 Under each node will be an SQLite file, which should remain in consensus. You can create clusters of any size, but clusters of 3, 5, and 7 nodes are most practical. Clusters larger than this become impractical, due to the number of nodes that must be contacted before a change can take place. -### Restarting a node -If a node needs to be restarted, perhaps because of failure, don't pass the `-join` option. Using the example nodes above, if node 2 needed to be restarted, do so as follows: - -```bash -rqlited -http localhost:4005 -raft :4006 ~/node.3 -``` - -On restart it will rejoin the cluster and apply any changes to the local SQLite database that took place while it was down. Depending on the number of changes in the Raft log, restarts may take a little while. +When restarting a node, there is no further need to pass `-join`. It will be ignored if a node is already a member of a cluster. ## Data API rqlite exposes an HTTP API allowing the database to be modified such that the changes are replicated. Queries are also executed using the HTTP API, though the SQLite database could be queried directly. Modifications go through the Raft log, ensuring only changes committed by a quorum of rqlite nodes are actually executed against the SQLite database. Queries do not __necessarily__ go through the Raft log, however, since they do not change the state of the database, and therefore do not need to be captured in the log. More on this later. diff --git a/cmd/rqlited/main.go b/cmd/rqlited/main.go index 657dd67d..db12399c 100644 --- a/cmd/rqlited/main.go +++ b/cmd/rqlited/main.go @@ -150,10 +150,14 @@ func main() { // If join was specified, make the join request. if joinAddr != "" { - if err := join(joinAddr, noVerify, raftAddr); err != nil { - log.Fatalf("failed to join node at %s: %s", joinAddr, err.Error()) + if !store.JoinRequired() { + log.Println("node is already member of cluster, ignoring join request") + } else { + if err := join(joinAddr, noVerify, raftAddr); err != nil { + log.Fatalf("failed to join node at %s: %s", joinAddr, err.Error()) + } + log.Println("successfully joined node at", joinAddr) } - log.Println("successfully joined node at", joinAddr) } // Create HTTP server and load authentication information, if supplied. diff --git a/store/store.go b/store/store.go index e281076c..9c0be82a 100644 --- a/store/store.go +++ b/store/store.go @@ -119,11 +119,12 @@ type Store struct { mu sync.RWMutex // Sync access between queries and snapshots. - ln *networkLayer // Raft network between nodes. - raft *raft.Raft // The consensus mechanism. - dbConf *DBConfig // SQLite database config. - dbPath string // Path to underlying SQLite file, if not in-memory. - db *sql.DB // The underlying SQLite store. + ln *networkLayer // Raft network between nodes. + raft *raft.Raft // The consensus mechanism. + dbConf *DBConfig // SQLite database config. + dbPath string // Path to underlying SQLite file, if not in-memory. + db *sql.DB // The underlying SQLite store. + joinRequired bool // Whether an explicit join is required. metaMu sync.RWMutex meta *clusterMeta @@ -181,6 +182,7 @@ func (s *Store) Open(enableSingle bool) error { if err != nil { return err } + s.joinRequired = len(peers) <= 1 // Allow the node to entry single-mode, potentially electing itself, if // explicitly enabled and there is only 1 node in the cluster already. @@ -240,6 +242,11 @@ func (s *Store) Close(wait bool) error { return nil } +// JoinRequired returns whether the node needs to join a cluster after being opened. +func (s *Store) JoinRequired() bool { + return s.joinRequired +} + // Path returns the path to the store's storage directory. func (s *Store) Path() string { return s.raftDir