Compare commits

...

33 Commits

Author SHA1 Message Date
d6523fe463 bump to v0.4.7 2015-02-10 15:59:33 -08:00
c25127a699 Merge pull request #2262 from yichengq/047
server: forbid /v2/stats/leader on follower
2015-02-09 22:29:48 -08:00
9f031e6218 server: forbid /v2/stats/leader on follower 2015-02-09 14:50:34 -08:00
e55724e959 Merge pull request #2260 from yichengq/047
server: refresh commit index when someone rejoins
2015-02-09 14:35:00 -08:00
29af192e3d server: refresh commit index when someone rejoins
Update commit index when rejoin happens, because 2.0 doesn't accept
more than one uncommitted config entry.
2015-02-09 14:28:30 -08:00
2fc79912c2 Merge pull request #2194 from yichengq/o3
server: standby exits when detecting v2 is running
2015-01-30 09:32:19 -08:00
ebb8d781b5 server: standby exits when detecting v2 is running 2015-01-29 16:26:47 -08:00
2e30b3c17f Merge pull request #2130 from xiang90/047-version
server: add internal version
2015-01-22 15:23:38 -08:00
9a2d82854e server: add internal version 2015-01-22 15:23:15 -08:00
b077dcf6c4 Merge pull request #2125 from xiang90/047-handler
next-version-handler
2015-01-22 11:40:54 -08:00
2b572cb6e8 Merge pull request #2126 from yichengq/o1
etcd: register usable versions when bootstrap
2015-01-22 11:32:23 -08:00
f36d55f062 next-version-handler 2015-01-22 11:20:52 -08:00
9f70568a02 etcd: register usable versions when bootstrap 2015-01-22 11:08:58 -08:00
1ca7d1e064 Merge pull request #2124 from xiang90/047-version
server: add version monitoring
2015-01-22 10:38:27 -08:00
4f1f003d04 server: add version monitoring 2015-01-22 10:23:15 -08:00
49e0dff2b8 CHANGELOG: v0.4.6 2014-07-29 10:31:48 -07:00
686837227e fix(docs/api): mention version endpoint is per instance 2014-07-28 11:28:16 -07:00
f2652f005e Merge pull request #882 from cap10morgan/patch-1
add the /version endpoint to the API docs
2014-07-28 11:26:24 -07:00
5490eb5406 Merge pull request #907 from MattParker89/patch-1
Adds units of time to flag docs
2014-07-27 22:37:42 -07:00
70dda950ed Adds units of time to flag docs
This fixes issue #905 based on the comments provided in server/cluster_config.go
2014-07-27 00:39:02 -04:00
a884f2a18a Merge pull request #881 from unihorn/111
standby server: save Running info correctly
2014-07-24 17:29:25 -07:00
bdeb96be0f Merge pull request #900 from unihorn/fix-timer
server: fix timer leak
2014-07-21 16:37:04 -07:00
c00594e680 server: fix timer leak 2014-07-21 14:23:52 -07:00
919cd380ec Merge pull request #883 from philips/add-consistent-note
Documentation: add note about consistent=true
2014-07-14 14:39:38 -08:00
b83aec6b87 Merge pull request #889 from robszumski/uuid
fix(docs): name defaults to uuid not hostname
2014-07-14 14:13:55 -07:00
05bfb369ef Documentation: add note about consistent=true 2014-07-13 11:07:59 -08:00
0639c4c86d fix(docs): name defaults to uuid not hostname 2014-07-11 11:30:05 -07:00
877b3d51bb Merge pull request #886 from christriddle/master
Added Access-Control-Allow-Headers for CORS requests
2014-07-10 13:05:07 -07:00
d9df58beb8 Added Access-Control-Allow-Headers to allow cors requests with those headers 2014-07-10 17:02:41 +01:00
1cffdb3a48 Merge pull request #866 from coreos/qread
feat(get): get from quorum
2014-07-08 18:30:18 -07:00
0593a52107 add the /version endpoint to the API docs
I had to find this by accident. It should be documented here.
2014-07-08 17:26:47 -04:00
f7854c4ab9 standby server: save Running info correctly
Running should be true when Start, and set to false when switching to
the other mode.
2014-07-08 08:29:17 -07:00
973bde9a07 feat(get): get from quorum 2014-06-22 21:33:38 -07:00
18 changed files with 290 additions and 35 deletions

View File

@ -1,3 +1,10 @@
v0.4.6
* Fix long-term timer leak (#900, #875, #868, #904)
* Fix `Running` field in standby_info file (#881)
* Add `quorum=true` query parameter for GET requests (#866, #883)
* Add `Access-Control-Allow-Headers` header for CORS requests (#886)
* Various documentation improvements (#907, #882)
v0.4.5
* Flush headers immediatly on `wait=true` requests (#877)
* Add `ETCD_HTTP_READ_TIMEOUT` and `ETCD_HTTP_WRITE_TIMEOUT` (#880)

View File

@ -13,6 +13,14 @@ This will bring up etcd listening on default ports (4001 for client communicatio
The `-data-dir machine0` argument tells etcd to write machine configuration, logs and snapshots to the `./machine0/` directory.
The `-name machine0` tells the rest of the cluster that this machine is named machine0.
## Getting the etcd version
The etcd version of a specific instance can be obtained from the `/version` endpoint.
```sh
curl -L http://127.0.0.1:4001/version
```
## Key Space Operations
The primary API of etcd is a hierarchical key space.
@ -833,6 +841,8 @@ curl -L http://127.0.0.1:4001/v2/keys/afile -XPUT --data-urlencode value@afile.t
### Read Consistency
#### Read from the Master
Followers in a cluster can be behind the leader in their copy of the keyspace.
If your application wants or needs the most up-to-date version of a key then it should ensure it reads from the current leader.
By using the `consistent=true` flag in your GET requests, etcd will make sure you are talking to the current master.
@ -843,6 +853,19 @@ The client is told the write was successful and the keyspace is updated.
Meanwhile F2 has partitioned from the network and will have an out-of-date version of the keyspace until the partition resolves.
Since F2 missed the most recent write, a client reading from F2 will have an out-of-date version of the keyspace.
Implementation notes on `consistent=true`: If the leader you are talking to is
partitioned it will be unable to determine if it is not currently the master.
In a later version we will provide a mechanism to set an upperbound of time
that the current master can be unable to contact the quorom and still serve
reads.
### Read Linearization
If you want a read that is fully linearized you can use a `quorum=true` GET.
The read will take a very similar path to a write and will have a similar
speed. If you are unsure if you need this feature feel free to email etcd-dev
for advice.
## Lock Module (*Deprecated and Removed*)
The lock module is used to serialize access to resources used by clients.

View File

@ -26,7 +26,7 @@ The full documentation is contained in the [API docs](https://github.com/coreos/
### Required
* `-name` - The node name. Defaults to the hostname.
* `-name` - The node name. Defaults to a UUID.
### Optional
@ -55,8 +55,8 @@ The full documentation is contained in the [API docs](https://github.com/coreos/
* `-peer-heartbeat-interval` - The number of milliseconds in between heartbeat requests
* `-snapshot=false` - Disable log snapshots. Defaults to `true`.
* `-cluster-active-size` - The expected number of instances participating in the consensus protocol. Only applied if the etcd instance is the first peer in the cluster.
* `-cluster-remove-delay` - The delay before one node is removed from the cluster since it cannot be connected at all. Only applied if the etcd instance is the first peer in the cluster.
* `-cluster-sync-interval` - The interval between synchronization for standby-mode instance with the cluster. Only applied if the etcd instance is the first peer in the cluster.
* `-cluster-remove-delay` - The number of seconds before one node is removed from the cluster since it cannot be connected at all. Only applied if the etcd instance is the first peer in the cluster.
* `-cluster-sync-interval` - The number of seconds between synchronization for standby-mode instance with the cluster. Only applied if the etcd instance is the first peer in the cluster.
* `-v` - Enable verbose logging. Defaults to `false`.
* `-vv` - Enable very verbose logging. Defaults to `false`.
* `-version` - Print the version and exit.

View File

@ -1,6 +1,6 @@
# etcd
README version 0.4.5
README version 0.4.7
A highly-available key value store for shared configuration and service discovery.
etcd is inspired by [Apache ZooKeeper][zookeeper] and [doozer][doozer], with a focus on being:

View File

@ -309,6 +309,7 @@ func (e *Etcd) runServer() {
for {
if e.mode == PeerMode {
log.Infof("%v starting in peer mode", e.Config.Name)
go registerAvailableInternalVersions(e.Config.Name, e.Config.Addr, e.Config.EtcdTLSInfo())
// Starting peer server should be followed close by listening on its port
// If not, it may leave many requests unaccepted, or cannot receive heartbeat from the cluster.
// One severe problem caused if failing receiving heartbeats is when the second node joins one-node cluster,

59
etcd/upgrade.go Normal file
View File

@ -0,0 +1,59 @@
package etcd
import (
"fmt"
"os"
"runtime"
"time"
"github.com/coreos/etcd/log"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
var defaultEtcdBinaryDir = "/usr/libexec/etcd/internal_versions/"
func registerAvailableInternalVersions(name string, addr string, tls *server.TLSInfo) {
var c *etcd.Client
if tls.Scheme() == "http" {
c = etcd.NewClient([]string{addr})
} else {
var err error
c, err = etcd.NewTLSClient([]string{addr}, tls.CertFile, tls.KeyFile, tls.CAFile)
if err != nil {
log.Fatalf("client TLS error: %v", err)
}
}
vers, err := getInternalVersions()
if err != nil {
log.Infof("failed to get local etcd versions: %v", err)
return
}
for _, v := range vers {
for {
_, err := c.Set("/_etcd/available-internal-versions/"+v+"/"+name, "ok", 0)
if err == nil {
break
}
time.Sleep(time.Second)
}
}
log.Infof("%s: available_internal_versions %s is registered into key space successfully.", name, vers)
}
func getInternalVersions() ([]string, error) {
if runtime.GOOS != "linux" {
return nil, fmt.Errorf("unmatched os version %v", runtime.GOOS)
}
etcdBinaryDir := os.Getenv("ETCD_BINARY_DIR")
if etcdBinaryDir == "" {
etcdBinaryDir = defaultEtcdBinaryDir
}
dir, err := os.Open(etcdBinaryDir)
if err != nil {
return nil, err
}
defer dir.Close()
return dir.Readdirnames(-1)
}

View File

@ -54,6 +54,7 @@ type CORSHandler struct {
func (h *CORSHandler) addHeader(w http.ResponseWriter, origin string) {
w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Add("Access-Control-Allow-Origin", origin)
w.Header().Add("Access-Control-Allow-Headers", "accept, content-type")
}
// ServeHTTP adds the correct CORS headers based on the origin and returns immediately

View File

@ -46,6 +46,7 @@ func (c *JoinCommand) NodeName() string {
// applyJoin attempts to join a machine to the cluster.
func applyJoin(c *JoinCommand, context raft.Context) (uint64, error) {
ps, _ := context.Server().Context().(*PeerServer)
ps.raftServer.FlushCommitIndex()
commitIndex := context.CommitIndex()
// Make sure we're not getting a cached value from the registry.

View File

@ -6,6 +6,7 @@ import (
"math/rand"
"net/http"
"net/url"
"os"
"sort"
"strings"
"sync"
@ -292,6 +293,7 @@ func (s *PeerServer) Start(snapshot bool, clusterConfig *ClusterConfig) error {
s.startRoutine(s.monitorTimeoutThreshold)
s.startRoutine(s.monitorActiveSize)
s.startRoutine(s.monitorPeerActivity)
s.startRoutine(s.monitorVersion)
// open the snapshot
if snapshot {
@ -370,6 +372,7 @@ func (s *PeerServer) HTTPHandler() http.Handler {
router.HandleFunc("/v2/admin/machines", s.getMachinesHttpHandler).Methods("GET")
router.HandleFunc("/v2/admin/machines/{name}", s.getMachineHttpHandler).Methods("GET")
router.HandleFunc("/v2/admin/machines/{name}", s.RemoveHttpHandler).Methods("DELETE")
router.HandleFunc("/v2/admin/next-internal-version", s.NextInternalVersionHandler).Methods("GET")
return router
}
@ -772,9 +775,9 @@ func (s *PeerServer) startRoutine(f func()) {
func (s *PeerServer) monitorSnapshot() {
for {
timer := time.NewTimer(s.snapConf.checkingInterval)
defer timer.Stop()
select {
case <-s.closeChan:
timer.Stop()
return
case <-timer.C:
}
@ -807,6 +810,8 @@ func (s *PeerServer) monitorSync() {
// monitorTimeoutThreshold groups timeout threshold events together and prints
// them as a single log line.
func (s *PeerServer) monitorTimeoutThreshold() {
ticker := time.NewTicker(ThresholdMonitorTimeout)
defer ticker.Stop()
for {
select {
case <-s.closeChan:
@ -815,12 +820,10 @@ func (s *PeerServer) monitorTimeoutThreshold() {
log.Infof("%s: warning: heartbeat near election timeout: %v", s.Config.Name, value)
}
timer := time.NewTimer(ThresholdMonitorTimeout)
defer timer.Stop()
select {
case <-s.closeChan:
return
case <-timer.C:
case <-ticker.C:
}
}
}
@ -828,13 +831,13 @@ func (s *PeerServer) monitorTimeoutThreshold() {
// monitorActiveSize has the leader periodically check the status of cluster
// nodes and swaps them out for standbys as needed.
func (s *PeerServer) monitorActiveSize() {
ticker := time.NewTicker(ActiveMonitorTimeout)
defer ticker.Stop()
for {
timer := time.NewTimer(ActiveMonitorTimeout)
defer timer.Stop()
select {
case <-s.closeChan:
return
case <-timer.C:
case <-ticker.C:
}
// Ignore while this peer is not a leader.
@ -864,13 +867,13 @@ func (s *PeerServer) monitorActiveSize() {
// monitorPeerActivity has the leader periodically for dead nodes and demotes them.
func (s *PeerServer) monitorPeerActivity() {
ticker := time.NewTicker(PeerActivityMonitorTimeout)
defer ticker.Stop()
for {
timer := time.NewTimer(PeerActivityMonitorTimeout)
defer timer.Stop()
select {
case <-s.closeChan:
return
case <-timer.C:
case <-ticker.C:
}
// Ignore while this peer is not a leader.
@ -895,3 +898,30 @@ func (s *PeerServer) monitorPeerActivity() {
}
}
}
func (s *PeerServer) monitorVersion() {
for {
select {
case <-s.closeChan:
return
case <-time.After(time.Second):
}
resp, err := s.store.Get("/_etcd/next-internal-version", false, false)
if err != nil {
continue
}
// only support upgrading to etcd2
if *resp.Node.Value == "2" {
log.Infof("%s: detected next internal version 2, exit after 10 seconds.", s.Config.Name)
} else {
log.Infof("%s: detected invaild next internal version %s", s.Config.Name, *resp.Node.Value)
continue
}
time.Sleep(10 * time.Second)
// be nice to raft. try not to corrupt log file.
go s.raftServer.Stop()
time.Sleep(time.Second)
os.Exit(0)
}
}

View File

@ -3,6 +3,7 @@ package server
import (
"encoding/json"
"net/http"
"path"
"strconv"
"time"
@ -309,6 +310,48 @@ func (ps *PeerServer) UpgradeHttpHandler(w http.ResponseWriter, req *http.Reques
w.WriteHeader(http.StatusOK)
}
func (ps *PeerServer) NextInternalVersionHandler(w http.ResponseWriter, req *http.Request) {
for i := 0; i < 50; i++ {
if ps.raftServer.State() != raft.Leader {
l := ps.raftServer.Leader()
if l == "" {
time.Sleep(5 * time.Second)
continue
}
url, _ := ps.registry.PeerURL(l)
uhttp.Redirect(url, w, req)
return
}
resp, err := ps.store.Get("/_etcd/available-internal-versions/2", true, true)
if err != nil {
time.Sleep(5 * time.Second)
continue
}
available := make(map[string]bool)
for _, n := range resp.Node.Nodes {
available[path.Base(n.Key)] = true
}
notfound := false
for _, n := range ps.registry.Names() {
if !available[n] {
notfound = true
break
}
}
if notfound {
time.Sleep(5 * time.Second)
continue
}
c := ps.store.CommandFactory().CreateSetCommand("/_etcd/next-internal-version", false, "2", store.Permanent)
_, err = ps.raftServer.Do(c)
if err == nil {
return
}
}
w.WriteHeader(http.StatusServiceUnavailable)
}
// machineMessage represents information about a peer or standby in the registry.
type machineMessage struct {
Name string `json:"name"`

View File

@ -1,3 +0,0 @@
package server
const ReleaseVersion = "0.4.5"

View File

@ -285,7 +285,7 @@ func (s *Server) Dispatch(c raft.Command, w http.ResponseWriter, req *http.Reque
// Handler to return the current version of etcd.
func (s *Server) GetVersionHandler(w http.ResponseWriter, req *http.Request) error {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "etcd %s", ReleaseVersion)
fmt.Fprintf(w, `{"releaseVersion":"%s","internalVersion":"%s"}`, ReleaseVersion, InternalVersion)
return nil
}
@ -324,12 +324,8 @@ func (s *Server) GetLeaderStatsHandler(w http.ResponseWriter, req *http.Request)
return nil
}
leader := s.peerServer.RaftServer().Leader()
if leader == "" {
return etcdErr.NewError(300, "", s.Store().Index())
}
hostname, _ := s.registry.ClientURL(leader)
uhttp.Redirect(hostname, w, req)
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("not current leader"))
return nil
}

View File

@ -30,6 +30,7 @@ type StandbyServerConfig struct {
}
type standbyInfo struct {
// stay running in standby mode
Running bool
Cluster []*machineMessage
SyncInterval float64
@ -78,12 +79,16 @@ func (s *StandbyServer) Start() {
s.removeNotify = make(chan bool)
s.closeChan = make(chan bool)
s.Running = true
if err := s.saveInfo(); err != nil {
log.Warnf("error saving cluster info for standby")
}
s.routineGroup.Add(1)
go func() {
defer s.routineGroup.Done()
s.monitorCluster()
}()
s.Running = true
}
// Stop stops the server gracefully.
@ -97,11 +102,6 @@ func (s *StandbyServer) Stop() {
close(s.closeChan)
s.routineGroup.Wait()
if err := s.saveInfo(); err != nil {
log.Warnf("error saving cluster info for standby")
}
s.Running = false
}
// RemoveNotify notifies the server is removed from standby mode and ready
@ -178,13 +178,21 @@ func (s *StandbyServer) redirectRequests(w http.ResponseWriter, r *http.Request)
// monitorCluster assumes that the machine has tried to join the cluster and
// failed, so it waits for the interval at the beginning.
func (s *StandbyServer) monitorCluster() {
ticker := time.NewTicker(time.Duration(int64(s.SyncInterval * float64(time.Second))))
defer ticker.Stop()
for {
timer := time.NewTimer(time.Duration(int64(s.SyncInterval * float64(time.Second))))
defer timer.Stop()
select {
case <-s.closeChan:
return
case <-timer.C:
case <-ticker.C:
}
ok, err := s.checkMemberInternalVersionIsV2()
if err != nil {
log.Warnf("fail checking internal version(%v): %v", s.ClusterURLs(), err)
} else if ok {
log.Infof("Detect the cluster has been upgraded to v2. Exit now.")
os.Exit(0)
}
if err := s.syncCluster(nil); err != nil {
@ -204,6 +212,10 @@ func (s *StandbyServer) monitorCluster() {
}
log.Infof("join through leader %v", leader.PeerURL)
s.Running = false
if err := s.saveInfo(); err != nil {
log.Warnf("error saving cluster info for standby")
}
go func() {
s.Stop()
close(s.removeNotify)
@ -212,6 +224,39 @@ func (s *StandbyServer) monitorCluster() {
}
}
func (s *StandbyServer) checkMemberInternalVersionIsV2() (bool, error) {
c := &http.Client{Transport: s.client.Client.Transport}
for _, memb := range s.Cluster {
url := memb.ClientURL
resp, err := c.Get(url + "/version")
if err != nil {
log.Debugf("failed to get /version from %s", url)
continue
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Debugf("failed to read body from %s", url)
continue
}
var m map[string]string
err = json.Unmarshal(b, &m)
if err != nil {
log.Debugf("failed to unmarshal body %s from %s", b, url)
continue
}
switch m["internalVersion"] {
case "1":
return false, nil
case "2":
return true, nil
default:
log.Warnf("unrecognized internal version %s from %s", m["internalVersion"], url)
}
}
return false, fmt.Errorf("failed to get version")
}
func (s *StandbyServer) syncCluster(peerURLs []string) error {
peerURLs = append(s.ClusterURLs(), peerURLs...)

View File

@ -17,6 +17,14 @@ func GetHandler(w http.ResponseWriter, req *http.Request, s Server) error {
vars := mux.Vars(req)
key := "/" + vars["key"]
recursive := (req.FormValue("recursive") == "true")
sort := (req.FormValue("sorted") == "true")
if req.FormValue("quorum") == "true" {
c := s.Store().CommandFactory().CreateGetCommand(key, recursive, sort)
return s.Dispatch(c, w, req)
}
// Help client to redirect the request to the current leader
if req.FormValue("consistent") == "true" && s.State() != raft.Leader {
leader := s.Leader()
@ -35,8 +43,6 @@ func GetHandler(w http.ResponseWriter, req *http.Request, s Server) error {
return nil
}
recursive := (req.FormValue("recursive") == "true")
sort := (req.FormValue("sorted") == "true")
waitIndex := req.FormValue("waitIndex")
stream := (req.FormValue("stream") == "true")

View File

@ -1,3 +1,5 @@
package server
const ReleaseVersion = "0.4.7"
const InternalVersion = "1"
const Version = "v2"

View File

@ -24,6 +24,7 @@ type CommandFactory interface {
prevIndex uint64, expireTime time.Time) raft.Command
CreateCompareAndDeleteCommand(key string, prevValue string, prevIndex uint64) raft.Command
CreateSyncCommand(now time.Time) raft.Command
CreateGetCommand(key string, recursive, sorted bool) raft.Command
}
// RegisterCommandFactory adds a command factory to the global registry.

View File

@ -89,3 +89,11 @@ func (f *CommandFactory) CreateSyncCommand(now time.Time) raft.Command {
Time: time.Now(),
}
}
func (f *CommandFactory) CreateGetCommand(key string, recursive, sorted bool) raft.Command {
return &GetCommand{
Key: key,
Recursive: recursive,
Sorted: sorted,
}
}

35
store/v2/get_command.go Normal file
View File

@ -0,0 +1,35 @@
package v2
import (
"github.com/coreos/etcd/log"
"github.com/coreos/etcd/store"
"github.com/coreos/etcd/third_party/github.com/goraft/raft"
)
func init() {
raft.RegisterCommand(&GetCommand{})
}
// The GetCommand gets a key from the Store.
type GetCommand struct {
Key string `json:"key"`
Recursive bool `json:"recursive"`
Sorted bool `json:sorted`
}
// The name of the get command in the log
func (c *GetCommand) CommandName() string {
return "etcd:get"
}
// Get the key
func (c *GetCommand) Apply(context raft.Context) (interface{}, error) {
s, _ := context.Server().StateMachine().(store.Store)
e, err := s.Get(c.Key, c.Recursive, c.Sorted)
if err != nil {
log.Debug(err)
return nil, err
}
return e, nil
}