Browse Source

Merge branch 'master' of https://github.com/coreos/etcd into mod-leader

Ben Johnson 12 years ago
parent
commit
2ce587ebc7

+ 114 - 2
README.md

@@ -226,8 +226,7 @@ If the TTL has expired, the key will be deleted, and you will be returned a 100.
 }
 ```
 
-
-### Waiting for a change 
+### Waiting for a change
 
 We can watch for a change on a key and receive a notification by using long polling.
 This also works for child keys by passing `recursive=true` in curl.
@@ -273,6 +272,54 @@ curl -L http://127.0.0.1:4001/v2/keys/foo?wait=true\&waitIndex=7
 The watch command returns immediately with the same response as previous.
 
 
+### Using a directory TTL
+
+Like keys, directories in etcd can be set to expire after a specified number of seconds.
+You can do this by setting a TTL (time to live) on a directory when it is created with a `PUT`:
+
+```sh
+curl -L http://127.0.0.1:4001/v2/keys/dir -XPUT -d ttl=30 -d dir=true
+```
+
+```json
+{
+    "action": "set",
+    "node": {
+        "createdIndex": 17,
+        "dir": true,
+        "expiration": "2013-12-11T10:37:33.689275857-08:00",
+        "key": "/newdir",
+        "modifiedIndex": 17,
+        "ttl": 30
+    }
+}
+```
+
+The directories TTL can be refreshed by making an update.
+You can do this by making a PUT with `prevExist=true` and a new TTL.
+
+```sh
+curl -L http://127.0.0.1:4001/v2/keys/dir -XPUT -d ttl=30 -d dir=true -d prevExist=true
+```
+
+Keys that are under this directory work as usual, but when the directory expires a watcher on a key under the directory will get an expire event:
+
+```sh
+curl -X GET http://127.0.0.1:4001/v2/keys/dir/asdf\?consistent\=true\&wait\=true
+```
+
+```json
+{
+    "action": "expire",
+    "node": {
+        "createdIndex": 8,
+        "key": "/dir",
+        "modifiedIndex": 15
+    }
+}
+```
+
+
 ### Atomic Compare-and-Swap (CAS)
 
 Etcd can be used as a centralized coordination service in a cluster and `CompareAndSwap` is the most basic operation to build distributed lock service.
@@ -813,6 +860,71 @@ To do this just change the `-*-file` flags to `-peer-*-file`.
 
 If you are using SSL for server-to-server communication, you must use it on all instances of etcd.
 
+## Modules
+
+etcd has a number of modules that are built on top of the core etcd API.
+These modules provide things like dashboards, locks and leader election.
+
+### Dashboard
+
+An HTML dashboard can be found at `http://127.0.0.1:4001/mod/dashboard/```
+
+### Lock
+
+The Lock module implements a fair lock that can be used when lots of clients want access to a single resource.
+A lock can be associated with a name.
+The name is unique so if a lock tries to request a name that is already queued for a lock then it will find it and watch until that name obtains the lock.
+If you lock the same name on a key from two separate curl sessions they'll both return at the same time.
+
+Here's the API:
+
+**Acquire a lock (with no name) for "customer1"**
+
+```sh
+curl -X POST http://127.0.0.1:4001/mod/v2/lock/customer1?ttl=60
+```
+
+**Acquire a lock for "customer1" that is associated with the name "bar"**
+
+```sh
+curl -X POST http://127.0.0.1:4001/mod/v2/lock/customer1?ttl=60 -d name=bar
+```
+
+**Renew the TTL on the "customer1" lock for index 2**
+
+```sh
+curl -X PUT http://127.0.0.1:4001/mod/v2/lock/customer1?ttl=60 -d index=2
+```
+
+**Renew the TTL on the "customer1" lock for name "customer1"**
+
+```sh
+curl -X PUT http://127.0.0.1:4001/mod/v2/lock/customer1?ttl=60 -d name=bar
+```
+
+**Retrieve the current name for the "customer1" lock.**
+
+```sh
+curl http://127.0.0.1:4001/mod/v2/lock/customer1
+```
+
+**Retrieve the current index for the "customer1" lock**
+
+```sh
+curl http://127.0.0.1:4001/mod/v2/lock/customer1?field=index
+```
+
+**Delete the "customer1" lock with the index 2**
+
+```sh
+curl -X DELETE http://127.0.0.1:4001/mod/v2/lock/customer1?index=customer1
+```
+
+**Delete the "customer1" lock with the name "bar"**
+
+```sh
+curl -X DELETE http://127.0.0.1:4001/mod/v2/lock/customer1?name=bar
+```
 
 ## Contributing
 

+ 3 - 1
mod/dashboard/app/scripts/common/services/etcd.js

@@ -88,7 +88,9 @@ angular.module('etcd', [])
     return newStat('leader').get().then(function(response) {
       return newKey('/_etcd/machines/' + response.data.leader).get().then(function(response) {
         // TODO: do something better here p.s. I hate javascript
-        var data = JSON.parse('{"' + decodeURI(response.data.value.replace(/&/g, "\",\"").replace(/=/g,"\":\"")) + '"}');
+        var data = decodeURIComponent(response.data.node.value);
+        data = data.replace(/&/g, "\",\"").replace(/=/g,"\":\"");
+        data = JSON.parse('{"' + data + '"}');
         return data.etcd;
       });
     });

+ 2 - 2
mod/dashboard/app/scripts/controllers/browser.js

@@ -60,13 +60,13 @@ angular.module('etcdBrowser', ['ngRoute', 'etcd', 'timeRelative'])
       $('#etcd-browse-error').hide();
       // Looking at a directory if we got an array
       if (data.dir === true) {
-        $scope.list = data.kvs;
+        $scope.list = data.node.nodes;
         $scope.preview = 'etcd-preview-hide';
       } else {
         $scope.singleValue = data.value;
         $scope.preview = 'etcd-preview-reveal';
         $scope.key.getParent().get().success(function(data) {
-          $scope.list = data.kvs;
+          $scope.list = data.node.nodes;
         });
       }
       $scope.previewMessage = 'No key selected.';

File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/scripts-browser-modules.js.go


File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/scripts-browser-scripts.js.go


File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/scripts-stats-modules.js.go


File diff suppressed because it is too large
+ 0 - 0
mod/dashboard/resources/scripts-stats-scripts.js.go


+ 2 - 2
server/config.go

@@ -13,8 +13,8 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/coreos/etcd/log"
 	"github.com/BurntSushi/toml"
+	"github.com/coreos/etcd/log"
 )
 
 // The default location for the etcd configuration file.
@@ -69,7 +69,7 @@ type Config struct {
 	VeryVerbose      bool `toml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
 	HeartbeatTimeout int  `toml:"peer_heartbeat_timeout" env:"ETCD_PEER_HEARTBEAT_TIMEOUT"`
 	ElectionTimeout  int  `toml:"peer_election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"`
-	Peer struct {
+	Peer             struct {
 		Addr     string `toml:"addr" env:"ETCD_PEER_ADDR"`
 		BindAddr string `toml:"bind_addr" env:"ETCD_PEER_BIND_ADDR"`
 		CAFile   string `toml:"ca_file" env:"ETCD_PEER_CA_FILE"`

+ 1 - 3
server/cors_handler.go

@@ -25,7 +25,7 @@ import (
 )
 
 type corsHandler struct {
-	router *mux.Router
+	router      *mux.Router
 	corsOrigins map[string]bool
 }
 
@@ -74,5 +74,3 @@ func (h *corsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 
 	h.router.ServeHTTP(w, req)
 }
-
-

+ 18 - 18
server/peer_server.go

@@ -23,23 +23,23 @@ import (
 const retryInterval = 10
 
 type PeerServer struct {
-	raftServer     raft.Server
-	server         *Server
-	httpServer     *http.Server
-	listener       net.Listener
-	joinIndex      uint64
-	name           string
-	url            string
-	bindAddr       string
-	tlsConf        *TLSConfig
-	tlsInfo        *TLSInfo
-	followersStats *raftFollowersStats
-	serverStats    *raftServerStats
-	registry       *Registry
-	store          store.Store
-	snapConf       *snapshotConf
-	MaxClusterSize int
-	RetryTimes     int
+	raftServer       raft.Server
+	server           *Server
+	httpServer       *http.Server
+	listener         net.Listener
+	joinIndex        uint64
+	name             string
+	url              string
+	bindAddr         string
+	tlsConf          *TLSConfig
+	tlsInfo          *TLSInfo
+	followersStats   *raftFollowersStats
+	serverStats      *raftServerStats
+	registry         *Registry
+	store            store.Store
+	snapConf         *snapshotConf
+	MaxClusterSize   int
+	RetryTimes       int
 	HeartbeatTimeout time.Duration
 	ElectionTimeout  time.Duration
 }
@@ -81,7 +81,7 @@ func NewPeerServer(name string, path string, url string, bindAddr string, tlsCon
 			},
 		},
 		HeartbeatTimeout: defaultHeartbeatTimeout,
-		ElectionTimeout: defaultElectionTimeout,
+		ElectionTimeout:  defaultElectionTimeout,
 	}
 
 	// Create transporter for raft

+ 5 - 3
server/registry.go

@@ -38,14 +38,16 @@ func NewRegistry(s store.Store) *Registry {
 }
 
 // Adds a node to the registry.
-func (r *Registry) Register(name string, peerURL string, url string) error {
+func (r *Registry) Register(name string, peerURL string, machURL string) error {
 	r.Lock()
 	defer r.Unlock()
 
 	// Write data to store.
 	key := path.Join(RegistryKey, name)
-	value := fmt.Sprintf("raft=%s&etcd=%s", peerURL, url)
-	_, err := r.store.Create(key, false, value, false, store.Permanent)
+	v := url.Values{}
+	v.Set("raft", peerURL)
+	v.Set("etcd", machURL)
+	_, err := r.store.Create(key, false, v.Encode(), false, store.Permanent)
 	log.Debugf("Register: %s", name)
 	return err
 }

+ 1 - 1
server/server.go

@@ -269,7 +269,7 @@ func (s *Server) Dispatch(c raft.Command, w http.ResponseWriter, req *http.Reque
 			w.Header().Set("Content-Type", "application/json")
 			// etcd index should be the same as the event index
 			// which is also the last modified index of the node
-			w.Header().Add("X-Etcd-Index", fmt.Sprint(e.Index))
+			w.Header().Add("X-Etcd-Index", fmt.Sprint(e.Index()))
 			w.Header().Add("X-Raft-Index", fmt.Sprint(s.CommitIndex()))
 			w.Header().Add("X-Raft-Term", fmt.Sprint(s.Term()))
 

+ 1 - 1
store/store.go

@@ -541,7 +541,7 @@ func (s *store) checkDir(parent *node, dirName string) (*node, *etcdErr.Error) {
 			return node, nil
 		}
 
-		return nil, etcdErr.NewError(etcdErr.EcodeNotDir, parent.Path, s.CurrentIndex)
+		return nil, etcdErr.NewError(etcdErr.EcodeNotDir, node.Path, s.CurrentIndex)
 	}
 
 	n := newDir(s, path.Join(parent.Path, dirName), s.CurrentIndex+1, parent, parent.ACL, Permanent)

+ 2 - 2
tests/server_utils.go

@@ -26,11 +26,11 @@ func RunServer(f func(*server.Server)) {
 	store := store.New()
 	registry := server.NewRegistry(store)
 
-	ps := server.NewPeerServer(testName, path, "http://" + testRaftURL, testRaftURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, registry, store, testSnapshotCount)
+	ps := server.NewPeerServer(testName, path, "http://"+testRaftURL, testRaftURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, registry, store, testSnapshotCount)
 	ps.MaxClusterSize = 9
 	ps.ElectionTimeout = testElectionTimeout
 	ps.HeartbeatTimeout = testHeartbeatTimeout
-	s := server.New(testName, "http://" + testClientURL, testClientURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, ps, registry, store)
+	s := server.New(testName, "http://"+testClientURL, testClientURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, ps, registry, store)
 	ps.SetServer(s)
 
 	// Start up peer server.

Some files were not shown because too many files changed in this diff