Compare commits

...

394 Commits

Author SHA1 Message Date
f9d27c37aa Merge pull request #548 from philips/0.3.0
doc(CHANGELOG): document v0.3.0
2014-02-07 15:57:08 -08:00
ddc40e5b45 doc(CHANGELOG): document v0.3.0 2014-02-07 15:54:45 -08:00
0c2287b201 fix(scripts/build-release): use cross compilation 2014-02-07 15:50:57 -08:00
102d8e543d bump(mod/dashboard): rebuild the dashboard for the latest fixes 2014-02-07 15:17:00 -08:00
b4d5534c75 Merge pull request #546 from sym3tri/dashboard-tabindexes
fix(dashboard): disable some tab indexes.
2014-02-07 15:10:09 -08:00
4f72403acd Merge pull request #544 from bcwaldon/case-insensitive-query-params
fix(v2): Use case-insensitive check on bool query params
2014-02-07 15:09:51 -08:00
4c0c256a9d fix(dashboard): disable some tab indexes. 2014-02-07 15:09:30 -08:00
d0f254a278 Merge pull request #545 from bcwaldon/doc-prevNode
doc(api): Document prevNode
2014-02-07 15:08:28 -08:00
0696e5026f doc(api): Document prevNode 2014-02-07 15:03:15 -08:00
42363001b4 fix(v2): Use case-insensitive check on bool query params
Fix issue #261
2014-02-07 14:29:08 -08:00
ed3d63248a Merge pull request #543 from philips/bump-goraft
bump(github.com/coreos/raft): ef3280ce54f60fff98a72012f547ed2b3415841f
2014-02-07 12:33:25 -08:00
2a2714a4bf Merge pull request #514 from cenkalti/prevNode
feat(prevNode): add "prevNode" to "Set" response
2014-02-07 12:04:18 -08:00
27cb38f38c fix(Documentation): add etcd-dump to libraries and tools
Fixes #325
2014-02-07 11:35:19 -08:00
e8be43d4c6 Merge pull request #528 from coreos/restructure-dashboard
Restructure dashboard & bug fixes
2014-02-07 11:26:18 -08:00
76da437f29 bump(github.com/coreos/raft): ef3280ce54f60fff98a72012f547ed2b3415841f 2014-02-07 11:19:23 -08:00
b4635b0b80 Merge pull request #542 from philips/fix-the-build
Fix the tests
2014-02-07 07:01:00 -08:00
147235f8f5 fix(test.sh): re-add the config tests
These tests were left behind in the move to put config in its own
package.
2014-02-06 22:52:50 -08:00
aa5c8b8ffd test(config): unexport ETCD_DISCOVERY
if this is exported the next tests will try and use it and fail.
2014-02-06 22:52:18 -08:00
1b3481fe25 fix(server/peer_server): stop the raftServer in Stop()
Stop() the raftServer if we stop the peerServer so that tests that start
and stop PeerServers exit cleanly.
2014-02-06 22:10:10 -08:00
21d7d14178 chore(tests/discovery): remove errant debug statement 2014-02-06 22:10:10 -08:00
14f15c33fe fix(tests/server_utils): use a WaitGroup for RunServer
Make sure that all of the servers exit before we return by using a
WaitGroup in each handler. This was used to help track down the issue
with the TestDiscoverySecondPeerUp test and the hung go-etcd watcher.
2014-02-06 22:10:10 -08:00
bfbc539321 hack(tests/discovery): don't use go-etcd for watch
go-etcd has a bug in the watcher that holds open a goroutine. Avoid
goetcd for this operation until it is fixed.
2014-02-06 22:10:10 -08:00
5f124166eb feat(tests/discovery): use low retry interval
In TestDiscoverySecondPeerFirstNoResponse use a low retryinterval so the
test doesn't take forever.
2014-02-06 22:10:10 -08:00
468a68c96c feat(server): make the RetryInterval of PeerServer tunable
For tests and other environments it would be nice to be able to tune how
long to sleep between retries.
2014-02-06 22:10:09 -08:00
da3fe920cb fix(tests/v1_migration): add a -name flag
The info flag is ignored as of 1c91c167fc
so in order for this test to work we need to add `-name` flag.
2014-02-06 21:23:00 -08:00
3d9fc3846c fix(scripts/test-cluster): fix backwards logic on peers add 2014-02-06 21:15:38 -08:00
63fa35c99f refactor(tls): clarify & simplify tls configuration 2014-02-06 21:15:38 -08:00
68305181f9 Merge pull request #538 from xiangli-cmu/fix_hidden_watch
fix(watcher_hub)
2014-02-06 12:39:18 -05:00
c844fccf2a fix(watcher_hub) isHidden checks the length of the watchPath before getting subString of keyPath 2014-02-06 11:09:47 -05:00
f2452a4a3c fix(build): Use ngmin. Fix all introduced erros in previous commits. 2014-02-05 22:41:10 -08:00
4e21405647 fix(dashboard): bugs in key browser and stats page.
Fixed key add, key delete bugs. More refactoring.
2014-02-05 22:40:04 -08:00
06f990236c refactor(dashboard): Restructured front-end dashboard code. 2014-02-05 22:39:46 -08:00
8a172322ff Merge pull request #537 from xiangli-cmu/fix_hidden_watch
fix(watch hidden key)
2014-02-05 23:43:41 -05:00
1b5f9eb013 test (isHidden) add unit test for isHidden function 2014-02-05 23:32:12 -05:00
5851cb5b8d chrod(watcher_hub) add comment to isHidden function 2014-02-05 23:31:38 -05:00
ba98de6ef0 fix(watch hidden key) Fix hidden keys preventing deeper recursive watches from receiving events
If a watcher has given the correct hidden directory, we should allow it to watch the non-hidden events under that hidden directory. This pull request achieves this by checking if the path after the watching prefix has a "/_" which indicates a hidden key.
2014-02-05 22:34:41 -05:00
d2a0f8f2fd Merge pull request #535 from robszumski/master
fix(docs): explicitly create links instead of using the markdown parser
2014-02-05 16:53:56 -08:00
445b584333 fix(docs): explicitly create links instead of using the markdown parser 2014-02-05 16:52:31 -08:00
f7dae0de02 Merge pull request #533 from robszumski/master
fix(docs): add full command example and link to discovery spec
2014-02-05 16:29:43 -08:00
1c6a41dda4 Merge pull request #532 from philips/remove-info-file
feat(config): remove the info file
2014-02-05 16:21:27 -08:00
1c91c167fc feat(config): remove the info file
The info file was meant to help the user from accidently making a
mistake but often times it just confuses people:

https://github.com/coreos/etcd/issues/356
https://github.com/coreos/etcd/issues/531
https://github.com/coreos/etcd/issues/318

Lets remove the info file for this next release.
2014-02-05 16:20:50 -08:00
39518b463a Merge pull request #534 from philips/discovery-protocol-fix
fix(discovery): use prevExist instead of prevValue=init
2014-02-05 15:15:08 -08:00
cbdf4a738c fix(discovery): use prevExist instead of prevValue=init
Use PUT /_state?prevExist=true in the protocol instead of PUT
/_state?prevValue=init. This lets people point one vanilla etcd at the
key prefix of another vanilla etcd and have it just work.
2014-02-05 15:14:57 -08:00
1d4912b22f fix merge conflicts 2014-02-05 14:39:48 -08:00
bc7297c2d0 feat(docs): add cluster discovery documentation 2014-02-05 14:37:40 -08:00
39ddb29e63 Merge pull request #515 from robszumski/master
feat(docs): add cluster discovery documentation
2014-02-05 11:53:31 -08:00
fe35839a77 feat(docs): add cluster discovery documentation 2014-02-05 10:54:28 -08:00
297832ff91 Merge pull request #512 from philips/bootstrap-protocol
feat(discovery): initial working code
2014-02-05 09:27:52 -08:00
2d75ef0c7a feat(Documentation/discovery-protocol): explain heartbeating
Explain more information about how the TTL works and etcds role.
2014-02-05 09:27:40 -08:00
2822b9c579 tests(tests/functional): add tests for Discovery
This tests a variety of failure cases for the Discovery service
including:

- Initial leader failures
- Discovery service failures
- Positive tests for discovery working flawlessly
2014-02-05 09:27:39 -08:00
ff6090836c fix(tests/server_utils): add a metrics bucket
This is required to avoid getting nil pointer exceptions if a peer joins
this test server.
2014-02-05 09:27:39 -08:00
a8b07b1b48 chore(config): go fmt 2014-02-05 09:27:39 -08:00
8687dd3802 feat(discovery): fully working discovery now 2014-02-05 09:27:39 -08:00
40021ab72e bump(github.com/coreos/go-etcd): 526d936ffe75284ca80290ea6386f883f573c232 2014-02-05 09:27:39 -08:00
72514f8ab2 feat(bootstrap): initial working code
This is an initial version of the bootstrap code that seems to work
under the normal circumstances. I need to mock out a server that will
test out all of the error cases now.
2014-02-05 09:27:39 -08:00
40a8542c22 feat(bootstrap): wire up the flag
This wires up `-bootstrap-url` to some code (which crashes) :)
2014-02-05 09:27:39 -08:00
f56965b1c0 refactor(config): make config its own package
Refactor config into its own package. Trying to tease the config from
the server so that all of the control surfaces are exposed in the Server
for easier testing.
2014-02-05 09:27:39 -08:00
69922340f6 refactor(server): move utilities into pkg
like camlistore lets move these utilities into a `pkg` prefix.
2014-02-05 09:27:39 -08:00
0e50d9787a feat(*): bootstrap initial commit
Setup the flags, and checkin the docs. Lets do this!
2014-02-05 09:27:39 -08:00
9e43e726a9 Merge pull request #507 from philips/turn-snapshots-on-by-default
feat(*): enable snapshots by default
2014-02-05 09:08:43 -08:00
03cadc543f Merge pull request #525 from yifan-gu/fix_comments
fix some typos in comments in store.go
2014-02-04 11:46:29 -08:00
b61cf9cb8e fix a format error in libraries-and-tools.md 2014-02-04 14:30:40 -05:00
8d2a8e1c7a fix some typos in comments in store.go 2014-02-04 14:17:44 -05:00
72b393ca53 Merge pull request #519 from philips/fixup-server-tls-client-config
fix(server): fix client certificate verification
2014-02-03 17:33:45 -08:00
6398206e4f Merge pull request #1 from bcwaldon/fixup-server-tls-client-config
test(TLS): Add test coverage for etcd TLS
2014-02-03 17:33:34 -08:00
226c20c097 test(TLS): Add test coverage for etcd TLS 2014-02-03 17:32:24 -08:00
0b9c5c975e fix(test.sh): use . not source 2014-02-02 17:01:21 -08:00
272dc343ef Merge pull request #520 from philips/use-goven
Use goven for all third party dependencies
2014-02-02 16:58:07 -08:00
d7d20d1c3d bump(github.com/stretchr/testify): 9cc77fa25329013ce07362c7742952ff887361f2 2014-02-02 16:57:36 -08:00
2557992b70 fix(tests): use correct raft package 2014-02-02 16:57:36 -08:00
33be0e09fe fix(build/test.sh): use new GOPATH setup 2014-02-02 16:57:36 -08:00
13b6c1e684 chore(*): make everything use goven
for i in github.com/BurntSushi/toml github.com/coreos/go-etcd/etcd github.com/coreos/go-log/log github.com/gorilla/context github.com/rcrowley/go-metrics bitbucket.org/kardianos/osext github.com/coreos/go-systemd/journal github.com/coreos/raft code.google.com/p/goprotobuf/proto ; do goven  -copy -rewrite $i; done
2014-02-01 23:44:18 -08:00
ea8a353545 chore(*): gofmt everything 2014-02-01 23:44:10 -08:00
0566bf2d5d Revert "Fix compile bug in peer_server_handlers.go "
This reverts commit e1ed380f04.
2014-02-01 20:09:53 -08:00
93a129e55a Merge pull request #516 from augustoroman/patch-1
fix(server compilation): compile bug in peer_server_handlers.go
2014-02-01 19:52:08 -08:00
58e1f12240 doc(server): some basic docs on the tls_config object
This should be refactored but something to remember while refactoring.
2014-01-31 17:08:37 -08:00
0fa6d38574 fix(server): fix client certificate verification
In d0c4916fe9 the TLS CA Certificate
verification broke.

This was bisected using the following basic test:

```
./bin/etcd -f -name machine0 -data-dir machine0 -ca-file=/tmp/ca/ca.crt -cert-file=/tmp/ca/server.crt -key-file=/tmp/ca/server.key.insecure
```

And in another window doing

```
curl --key /tmp/ca/server2.key.insecure  --cert /tmp/ca/server2.crt -k -L https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
```

Before merging this PR there are a few things that need to be fixed up:

1) Tests for client certs both positive and negative
2) Refactor (or at least documentation of) the TLSConfig types
2014-01-31 16:56:15 -08:00
e1ed380f04 Fix compile bug in peer_server_handlers.go
resp.Success is a func() bool, not a bool.  Call it.
2014-01-30 15:31:36 -08:00
354a91290e feat(prevNode): add test for prevNode 2014-01-29 17:52:25 -08:00
3ec7004421 feat(prevNode): add "prevNode" to "Set" response 2014-01-29 17:30:33 -08:00
a542a7804b Merge pull request #508 from jonboulle/master
Various cleanup to API documentation
2014-01-25 13:06:29 -08:00
03ff4c8b76 Missed one 2014-01-25 12:22:37 -08:00
7992448f6a Various cleanup to API documentation 2014-01-25 12:08:57 -08:00
9a0ddb3760 feat(server): log on snapshot success or failure
lila.local: snapshot of 12 events completed at index 479
lila.local: snapshot of 12 events at index 491 attempted and failed: handling snapshot
2014-01-24 07:13:01 -08:00
7ee7e910eb feat(*): enable snapshots by default
Ben recently added test coverage for snapshots so we should enable it in
etcd. Lets do this.

1d66f6a111
2014-01-23 20:53:22 -08:00
281b0e7e59 Merge pull request #506 from philips/add-freebsd-docs
feat(Documentation): add instructions on freebsd
2014-01-23 16:44:38 -08:00
50e6256058 feat(Documentation): add instructions on freebsd 2014-01-23 16:43:38 -08:00
1b00c449a5 Revert "Better error message when setting values on directories"
This reverts commit d13dd50d51.
2014-01-23 11:22:11 -08:00
9848072d21 Merge pull request #487 from intjonathan/patch-1
English clarity in filesystem documentation.
2014-01-23 04:03:05 -08:00
2652e46d66 Merge pull request #504 from kelseyhightower/master
Better error message when setting values on directories
2014-01-23 03:59:40 -08:00
d13dd50d51 Better error message when setting values on directories
Without this commit etcd returns the following error message when
setting values on directories:

    {
      "errorCode":102,
      "message":"Not a file",
      "cause":"/postgres",
      "index":2
    }

While the above error message is accurate it's not very descriptive.
This commit adds a new error code/message which better describes why the
write operation failed. etcd now returns the following:

    {
      "errorCode":109,
      "message":"Cannot set value on directory",
      "cause":"/postgres",
      "index":2
    }
2014-01-22 23:02:33 -08:00
8fece992eb Update filesystem documenation for clarity. 2014-01-22 20:43:54 -08:00
3264b51a74 Merge pull request #501 from benbjohnson/snapshot-documentation
Add snapshot documentation
2014-01-22 15:26:53 -08:00
0692097a73 Add snapshot documentation. 2014-01-22 16:06:25 -07:00
19ef1042d6 Merge pull request #497 from philips/store-bench-no-rand
fix(store/store_bench): don't use rand
2014-01-22 13:19:04 -08:00
394e651591 Merge pull request #484 from bcwaldon/server-config
Refactor server init code
2014-01-22 11:39:27 -08:00
2fe22f1890 refactor(servers): emit http.Handlers from *Server 2014-01-22 11:17:58 -08:00
089021ca6d refacotor(transporter): make TLS config explicit 2014-01-22 11:17:58 -08:00
f158dfcd77 refactor(peerserver): Remove PeerServerConfig.Path 2014-01-22 11:17:58 -08:00
19980a7033 refactor(peerserver): remove timeouts from PeerServerConfig 2014-01-22 11:17:58 -08:00
a7d9efa900 refactor(server): Remove ServerConfig struct 2014-01-22 11:17:58 -08:00
0abd860f7e refactor(server): drop Serve code; rename cors object
* server/cors.go renamed to http/cors.go
* all CORS code removed from Server and PeerServer
* Server and PeerServer fulfill http.Handler, now passed to http.Serve
* non-HTTP code in PeerServer.Serve moved to PeerServer.Start
2014-01-22 11:17:57 -08:00
5c3a3db2d8 refactor(server): treat Server as an http.Handler 2014-01-22 11:17:57 -08:00
074099a1b2 refactor(cors): Simplify corsInfo struct 2014-01-22 11:17:57 -08:00
a2ee620394 refactor(raft): init raft transporter & server in main 2014-01-22 11:17:56 -08:00
ffa2b07dc4 refactor(transporter): Pass in everything the transporter needs 2014-01-22 11:17:43 -08:00
60bbc57aeb refactor(transporter): pass in timeouts 2014-01-22 11:17:43 -08:00
86718167e8 refactor(peer_server): move stats construction to factories 2014-01-22 11:17:42 -08:00
7bd4d05a38 refactor(peer-server): move listener init out of peer_server.go 2014-01-22 11:17:41 -08:00
d0c4916fe9 refactor(server): move listener init out of server.go 2014-01-22 11:17:26 -08:00
91fc6aabd2 chore(gofmt): Run gofmt 2014-01-22 11:17:26 -08:00
c0ff8f6026 chore(imports): Shift around some imports 2014-01-22 11:17:26 -08:00
a93d60be90 refactor(cors): Break apart CORS data and middleware 2014-01-22 11:17:26 -08:00
c47760382e refactor(Server): Use a config struct in Server 2014-01-22 11:17:25 -08:00
9c8a23c333 refactor(PeerServer): Use a config struct in PeerServer 2014-01-22 11:17:03 -08:00
91f768f9ae refactor(cors): Rename cors_handler.go to cors.go 2014-01-22 10:59:12 -08:00
6c48466bfe Merge pull request #500 from drusellers/patch-2
adding .net client to the matrix
2014-01-22 09:26:44 -08:00
5bf667851c adding .net client to the matrix 2014-01-22 10:00:22 -06:00
d77e3a203f Merge pull request #499 from drusellers/patch-1
adding .net client
2014-01-22 07:29:09 -08:00
c32dfa013d adding .net client 2014-01-22 09:24:54 -06:00
6b70efbe30 Merge pull request #498 from tobz/actually-hidden-keys
fix(store): properly hide hidden keys from watchers, not just gets
2014-01-22 06:52:31 -08:00
641edd4e6e test(store): group together all store tests that deal with hidden keys 2014-01-22 09:29:53 -05:00
823fdfab12 fix(store): make isHidden see if any portion of the path is hidden, not just the last element 2014-01-22 09:29:33 -05:00
0cacb6cba4 test(store): exercise watchers receiving notifications of non-hidden keys within hidden directories 2014-01-22 09:20:57 -05:00
7a948746a8 fix(store): move logic to handle whether or not to notify (re: hidden keys) entirely into watcher hub 2014-01-22 09:02:42 -05:00
139f59f7d1 fix(store): properly hide hidden keys from watchers, not just gets 2014-01-21 20:26:56 -05:00
80c22a4fb2 fix(store/store_bench): don't use rand
rand just introduces more noise to the results, don't use it.
2014-01-21 17:01:26 -08:00
a417782151 Merge pull request #483 from bcwaldon/metrics
Integrate go-metrics
2014-01-21 14:17:03 -08:00
e87f3231a1 Merge pull request #496 from bcwaldon/droneio
test(ci): Replace travis.ci with drone.io
2014-01-21 12:28:52 -08:00
fe39288ebf test(ci): Replace travis.ci with drone.io 2014-01-21 11:50:36 -08:00
97bc5b260d feat(metrics): Publish peer heartbeat events as metrics 2014-01-21 11:44:22 -08:00
47f24d1088 bump(github.com/coreos/raft): bf7accb84ce4fe446983abffe00dd18a6b8cbc18 2014-01-21 11:18:50 -08:00
3a75d0a465 Merge pull request #493 from xiangli-cmu/bench_watcher
test(store_bench_test.go) add watch bench
2014-01-21 07:35:18 -08:00
17c8f6d2e8 test(store_bench_test.go) add watch bench 2014-01-21 06:51:40 -05:00
7eaad5c8e0 feat(metrics): enable some metrics; push to graphite
* -trace flag controls whether or not to enable metrics-gathering
  and the /debug/* HTTP endpoints
* -graphite-host flag controls where metrics should be sent
* timer.ae.handle metric tracks execution time of AppendEntriesRequest
2014-01-20 15:39:36 -08:00
3e7c2dff96 feat(metrics): Add documentation and contrib scripts 2014-01-20 15:37:31 -08:00
14c96306a0 feat(metrics): Add metrics pkg 2014-01-20 13:32:42 -08:00
d122ed3bcd Merge pull request #492 from rwindelz/fix-ttl
fix(store): TTL should range 1..n rather than 1..n+1
2014-01-20 09:28:38 -08:00
451e874696 Merge pull request #480 from xiangli-cmu/store_bench
feat(store/store_bench_test.go) add a benchmark for set operation
2014-01-20 07:00:54 -08:00
290ca6bbc7 Merge pull request #491 from jkakar/master
Trivial typo fix in API documentation
2014-01-19 21:53:26 -08:00
a2e5bae951 fix(store): TTL should range 1..n rather than 1..n+1
was experiencing intermittent functional test fails where TTL was eg 101
when 100 was expected
informal testing on a windows platform shows Go times resolving to the
nanosecond but with an accuracy of approximately 1 millisecond
I believe some of the functional test steps would run in under a
millisecond and cause the TTL to be recomputed with the same time.Now()
value resulting in a TTL that was +1 from the expected
2014-01-19 21:45:53 -08:00
823e744ed9 - Fix typo. 2014-01-18 18:28:33 -08:00
35c89c7537 feat(metrics): Add github.com/rcrowley/go-metrics 2014-01-17 16:12:18 -08:00
77887e8253 fix(bench): remove trailing slash
Remove trailing slash. This works around
https://github.com/coreos/go-etcd/issues/82
2014-01-17 16:03:46 -08:00
1e6c0dee24 Merge pull request #488 from robszumski/master
feat(docs): Prepare to sync security doc to CoreOS website
2014-01-17 13:11:49 -08:00
e89e42382a fix(docs): remove header 2014-01-17 13:06:59 -08:00
184a5901e6 feat(docs): modify for sync to CoreOS website 2014-01-17 12:25:36 -08:00
6f8b0dc7ef add delete bench 2014-01-17 15:18:11 +08:00
21f0c6f9d4 feat(store_bench) add set bench for different value sizes 2014-01-17 14:19:31 +08:00
bd2b3793a6 Merge pull request #486 from philips/document-profiling-binary
fix(Documentation/profiling): note about using the right binary
2014-01-16 17:09:43 -08:00
25caac370f bump(github.com/coreos/go-log): 70d039bee4b0e389e5be560491d8291708506f59 2014-01-16 16:56:10 -08:00
0c8329a3fb fix(scripts/test-cluster): use v2 flags and find the path for etcd 2014-01-16 16:40:49 -08:00
444b5d329c fix(Documentation/profiling): note about using the right binary 2014-01-16 15:51:30 -08:00
7a7f6aea00 Merge pull request #479 from philips/add-debug-endpoint
add-debug-endpoint
2014-01-16 11:28:52 -08:00
b226b14eb2 add mem stats for sets benchmark 2014-01-16 16:06:18 +08:00
72b165ad4c Merge pull request #482 from bcwaldon/etcdbench
etcdbench -endpoint flag; error reporting
2014-01-15 22:37:33 -08:00
bd04905154 fix(etcdbench): Check for error in etcdbench set operation 2014-01-15 22:33:34 -08:00
471c40735c feat(etcdbench): Add -endpoint flag to etcdbench 2014-01-15 22:33:25 -08:00
c2d1dc4f51 add a setWithJson test 2014-01-16 09:16:33 +08:00
000290dc94 Merge pull request #481 from philips/fixup-test-cluster
fix(scripts/test-cluster): use ./bin/etcd now
2014-01-15 17:15:53 -08:00
3f3a324108 fix(scripts/test-cluster): use ./bin/etcd now 2014-01-15 17:15:18 -08:00
77477b3e43 feat(store/store_bench_test.go) add a benchmark for set operation of store pkg
We randomly generage N 3 level keys. We benchmark the speed of setting each key into etcd store.
2014-01-16 09:03:42 +08:00
c2077ed0b6 feat(server): add net/http/pprof endpoints
Add some basic profiling endpoints over http to start digging into
memory and request latencies.
2014-01-15 15:03:29 -08:00
ceefa98c76 Merge pull request #476 from ekristen/master
fixes bug with the etcd docker image not being able to run
2014-01-15 10:39:38 -08:00
d3fb9f0f0f etcd is in the bin directory, fixes bug with the docker image not working 2014-01-15 12:46:09 -05:00
d9088a5f18 Merge pull request #473 from bcwaldon/fix-peer-timeouts
Use election and heartbeat timeouts when building peer transporter
2014-01-15 02:11:40 -08:00
87113f985f Merge pull request #472 from benbjohnson/fix-error-codes
Fix mod/lock and mod/leader return error codes.
2014-01-15 01:14:59 -08:00
4da933e4a4 Merge pull request #475 from philips/fixup-travis
fix(travis): fixes from the third_party.go merge
2014-01-14 22:41:34 -08:00
56909bb6a3 fix(travis): fixes from the third_party.go merge 2014-01-14 22:36:40 -08:00
ecd73acc01 Merge pull request #460 from philips/use-third-party.go
chore(build): use third_party.go
2014-01-14 22:16:15 -08:00
e2e0853492 fix(server/release_version): checkin to git 2014-01-14 22:14:47 -08:00
0f97e3528a chore(build): use third_party.go
use the third_party.go project to replace our update script. This
requires moving a few things around and gets rid of a few annoying bugs:

- You can now bump individual packages
- A new src directory isn't created on build
- Less shell scripting!
- Things get built into ./bin/
2014-01-14 22:14:47 -08:00
89074ffcea Merge pull request #474 from jpetazzo/switch-to-go-1.2
Download and build Go 1.2 in the Dockerfile.
2014-01-14 21:54:14 -08:00
6b14fe7747 Download and build Go 1.2 in the Dockerfile.
The dependency BurntSushi/toml actually needs Go 1.2, because it uses
encoding.TextUnmarshaler, which didn't exist in Go 1.1. Since the PPA
that we use doesn't have Go 1.2 yet, we will use the same method as
Docker, i.e. download Go source tarball and compile it.
2014-01-14 18:19:37 -08:00
48e36422b5 chore(gofmt): Run gofmt on server/config.go 2014-01-14 09:18:09 -08:00
32df6f92fc fix(peer): Pass peer server timeouts through factory
The peer's heartbeat and election timeouts are needed to build
the transporter in the factory method.
2014-01-14 09:18:03 -08:00
cde184fdbf Fix mod/lock and mod/leader return error codes. 2014-01-14 07:57:30 -07:00
97f1363afa Merge pull request #470 from ranjib/consistent_curl_examples
style(Documentation/api): keep curl based examples consistent and correct
2014-01-12 21:54:39 -08:00
8b8b8d74fe style(Documentation/api): keep curl based examples consistent and correct
- fix curl examples where `http://` was missing.
- consistent passing HTTP method parameter (all examples now have -X ACTION at
 the end or the url, GET is implicit)
- quote url when it contains `&` , instead of escaping
2014-01-11 20:51:25 -08:00
5821a1390a Merge pull request #466 from bcwaldon/verbosity
Add more control over verbose logging
2014-01-10 14:30:32 -08:00
ae2130952b fix(config): Set VeryVerbose properly 2014-01-10 11:45:04 -08:00
b0cdf73565 feat(logging): Add VeryVeryVerbose opt to control raft trace info
Set very_very_verbose=true in a config file or use the -vvv CLI
option to get raft trace logs in addition to etcd debug logs.
2014-01-10 11:45:04 -08:00
c64c739fab Merge pull request #461 from xiangli-cmu/stream_watcher
feat(stream watchers) add stream watcher support
2014-01-10 08:42:31 -08:00
feb6f2dad7 Merge pull request #465 from philips/add-note-that-the-dahboard-is-compiled-in
feat(Documentation/modules): note the dashboard is compiled in
2014-01-10 06:10:36 -08:00
2c42271cea Merge pull request #18 from cenkalti/li
feat(stream watchers) fix locking issue
2014-01-10 06:07:14 -08:00
8597904bc2 feat(stream watchers) fix locking issue 2014-01-10 16:04:23 +02:00
227ea57c37 Merge pull request #17 from cenkalti/li
feat(stream watchers) disable double chunking
2014-01-10 05:17:44 -08:00
5b924dfd4e feat(stream watchers) disable double chunking 2014-01-10 15:09:35 +02:00
181805bb87 feat(Documentation/modules): note the dashboard is compiled in 2014-01-09 17:57:14 -08:00
63c06d6c79 Merge pull request #463 from robszumski/master
feat(docs): make clustering examples more complete
2014-01-09 17:07:12 -08:00
782166dadd feat(docs): make clustering examples more complete 2014-01-09 14:37:04 -08:00
7bfd11679b Merge pull request #462 from xiangli-cmu/sync_cnt_for_snapshot
fix(snapshot) count num of log entries rather than etcd transcations
2014-01-09 05:29:18 -08:00
f250649a5e fix(snapshot) count num of log entries rather than etcd transcations 2014-01-09 21:28:09 +08:00
629a827ee2 Merge pull request #16 from cenkalti/li
feat(stream watchers) end streaming if too many notifications
2014-01-09 04:21:26 -08:00
c247d807af feat(stream watchers) end streaming if too many notifications 2014-01-09 14:15:36 +02:00
22a25a18b3 feat(stream watchers) add stream watcher support 2014-01-09 15:28:33 +08:00
6b77b94127 Merge pull request #420 from benbjohnson/logging
Logging
2014-01-08 21:36:52 -08:00
2bfb8f5e4f Merge pull request #418 from xiangli-cmu/cancel_watcher
cancel watcher
2014-01-08 21:34:32 -08:00
dd6623be2f Merge pull request #454 from dsoprea/master
Added HTTPS for python-etcd-client.
2014-01-08 21:32:59 -08:00
fa3b4a7941 refactor(watcher) change newWatcher to Watch 2014-01-09 13:29:04 +08:00
8047cfd48e Merge pull request #447 from robszumski/master
Prepare docs to be synced to website
2014-01-08 16:38:44 -08:00
53477af1eb Merge branch 'master' of https://github.com/coreos/etcd into logging 2014-01-08 16:50:51 -07:00
355bd6df9b Fix Travis CI. 2014-01-08 16:41:01 -07:00
8d25dac1ba Revert test.sh changes. 2014-01-08 16:21:22 -07:00
b47042634a Add ThresholdMonitorTimeout. 2014-01-08 15:51:13 -07:00
e02441acb1 Merge pull request #455 from philips/fixup-cas-header
chore(Documentation/api): introduce acronym later in the text
2014-01-08 10:46:46 -08:00
ddd8e85146 chore(Documentation/api): introduce acronym later in the text 2014-01-08 10:46:05 -08:00
68546de2cb Merge pull request #444 from philips/document-stats-api
Documentation: document the stats API
2014-01-08 10:41:29 -08:00
7553a92232 feat(Documentation/api): document the store statistics 2014-01-08 10:40:57 -08:00
c7e642baa2 fix(Documentation/api): fixup the names in stats
This was fixed in #449
2014-01-08 10:36:44 -08:00
0377ea751d Added additional separator column. 2014-01-08 10:48:29 -05:00
5efad911d5 Added HTTPS for python-etcd-client. 2014-01-08 09:57:45 -05:00
d38a260e00 Merge pull request #452 from xiangli-cmu/fix_client-matrix_link
doc(Documentation/libraries-and-tools.md) fix link to clients-matrix
2014-01-07 22:32:51 -08:00
0cc2e8887f doc(Documentation/libraries-and-tools.md) fix link to clients-matrix 2014-01-08 14:26:56 +08:00
3736b7678a Merge pull request #449 from xiangli-cmu/fix_stats
fix(peer_server.go) init name field and update leader field
2014-01-07 19:21:30 -08:00
e373c9ac8c fix(docs): suggest cors flag for using dashboard 2014-01-07 17:08:15 -08:00
5f2d9b38dc fix(docs): reference correct cors flag 2014-01-07 17:03:45 -08:00
a7eeb01bc8 fix(docs): reference correct cors flag 2014-01-07 17:03:11 -08:00
c26215a740 Merge pull request #450 from dsoprea/master
Updated client matrix.
2014-01-07 16:06:10 -08:00
4e8828c5c4 Merge pull request #451 from philips/use-the-right-port
fix(README): use 4002 not 4001
2014-01-07 15:58:20 -08:00
2dde2cbb6f fix(README): use 4002 not 4001
The default port is 4001 not 4002. Fix this.
2014-01-07 15:57:18 -08:00
88e0263d08 Add heartbeat and timeout threshold loggers. 2014-01-07 16:17:48 -07:00
28652ed7af Updated client matrix. 2014-01-07 00:49:28 -05:00
60c2680bfd fix(peer_server.go) init name field and update leader field 2014-01-07 12:30:20 +08:00
9a8bd96ad3 feat(Documentation/api): add a statistics section 2014-01-05 23:06:56 -08:00
66271fe986 chore(Documentation/api): remove some whitespace 2014-01-05 23:05:15 -08:00
1caa3245e6 feat(Documentation/api): add some intro material
add some intro material and normalize the headers to the rest of the
coreos/docs.
2014-01-05 23:04:27 -08:00
04720bd8bf Merge pull request #442 from philips/remove-ETCD_WEB_URL
chore(server): remove web url
2014-01-05 20:41:28 -08:00
ecc96df699 chore(server): remove web url
web URL is not longer used so remove it from tests and configuration
documents.
2014-01-05 20:39:39 -08:00
0b47d6888d Merge pull request #441 from lavagetto/matrix
docs(clients-matrix.md): Introducing the library features matrix.
2014-01-05 20:27:10 -08:00
e9b85264ab docs(clients-matrix.md): Introducing the library features matrix.
As support for etcd features is very uneven between different clients,
a feature matrix can help application developers to understand what a
client library can offer them, and also help client libraries
developers to understand what they should work on. I assessed the
features of all client libraries by looking at their master branch on
github, to the best of my knowledge.
2014-01-05 11:30:11 +01:00
d397e83a05 Merge pull request #440 from philips/splitup-README
feat(README): splitup the sections into individual files
2014-01-04 17:59:17 -08:00
801b642dea fix(Documentation): fixup headers
fixup the headers on the api and tuning sections
2014-01-04 17:58:39 -08:00
e7527ebb45 fix(README): link to the configuration file too 2014-01-03 14:39:23 -08:00
bfde27a99d feat(README): splitup the sections into individual files
The README is getting rather large so split it into individual files.

The next step will be rendering these into HTML pages with a TOC so that
they are a bit more navigable.

What do people think of this?
2014-01-03 14:37:53 -08:00
c65f8fc7ad Merge pull request #439 from lavagetto/master
docs(README.md): update client libraries and projects.
2014-01-03 10:29:12 -08:00
547c05685e Merge pull request #438 from robszumski/master
feat(docs): add cluster size guide
2014-01-03 10:28:59 -08:00
5794d70881 docs(README.md): update client libraries and projects.
This commit adds information about API version support for all
clients - clearly listing which clients support API v2.

Also, a new nodejs client is added, and a new project as well.
2014-01-03 17:34:18 +01:00
009b84c5fc feat(docs): add cluster size guide 2014-01-02 16:11:25 -08:00
b5426d967e Fix TOML. 2014-01-02 16:48:50 -07:00
60827ebd5a bump(code.google.com/p/go.net): d4afe896f927+ 2014-01-02 16:41:38 -07:00
ae10b6226d bump(github.com/gorilla/mux): 9ede152210fa25c1377d33e867cb828c19316445 2014-01-02 16:41:33 -07:00
d32fd00bbb bump(github.com/coreos/go-systemd): 05a794e41e912eca8ebe1538241815f80da76d69 2014-01-02 16:41:31 -07:00
7c29d5bf5c bump(github.com/coreos/go-log/log): 2014-01-02 16:41:31 -07:00
f37d9df118 bump(github.com/coreos/go-etcd): 9de519a68a870466f217c35f476ba658f1694bbe 2014-01-02 16:41:28 -07:00
7da85d66fd bump(github.com/coreos/raft): 20e384681d014100733dd69d287fe254d685f319 2014-01-02 16:41:27 -07:00
cf656ccfdd bump(github.com/BurntSushi/toml): 2fffd0e6ca4b88558be4bcab497231c95270cd07 2014-01-02 16:41:25 -07:00
d7087ed61a Merge branch 'master' of https://github.com/coreos/etcd into logging 2014-01-02 16:30:09 -07:00
98351b9756 Merge pull request #436 from thwarted/help-version-fixups
docs(server/config.go): minor formatting changes
2014-01-02 13:50:19 -08:00
5260c7c939 Merge pull request #434 from rwindelz/master
fix (scripts/release-version.ps1): fix windows build
2014-01-02 12:39:15 -08:00
78e2fc1bc8 Merge pull request #437 from bcwaldon/README-indexes
chore(README): Expand README with more info about indexes
2014-01-02 12:33:49 -08:00
c17ad07bdb chore(README): Expand README with more info about indexes 2014-01-02 11:35:17 -08:00
5b105ed156 fix deprecated option tests 2014-01-02 11:11:27 -06:00
af3240fa18 docs(server/config.go): minor formatting changes
When -version or -help are given, don't warn about having to derive
the data directory name.
Print warnings about deprecated options on separate lines so the log
isn't screwy.
2014-01-02 04:30:38 -06:00
45b4d6d194 fix(build.ps1): not required to be admin to run build in windows
mklink /D in windows requires elevated privilege (Runas Administrator),
'/J' to create a directory junction point runs with user privilege
use an explicit fully qualified path for junction target
2014-01-01 22:23:07 -08:00
04ad7a91dd fix (scripts/release-version.ps1): fix windows build
fix reflects changes made in commit
7670c85d70
2014-01-01 21:16:40 -08:00
fd0d0813ce Merge pull request #432 from btipling/fix_trimsplit
Trimsplit Wasn't using separator, more efficient.
2014-01-01 13:46:08 -08:00
5a4c41be37 Don't copy strings. 2014-01-01 13:44:04 -08:00
57f94f65a8 Merge pull request #433 from xiangli-cmu/prevNode
feat(node_extern.go) add prevNode field
2014-01-01 12:59:41 -08:00
189b98c03f refactor(node_extern.go) remove unused prevValue field 2014-01-01 20:01:29 +08:00
f46fdbf078 feat(node_extern.go) add prevNode field 2014-01-01 19:50:07 +08:00
ed2d7d64cd Trimsplit Wasn't using separator, more efficient. 2013-12-31 09:13:41 -08:00
cc10b1084d Merge branch 'master' of https://github.com/coreos/etcd into logging
Conflicts:
	tests/functional/simple_snapshot_test.go
2013-12-30 16:19:57 -07:00
c49711ae2c Merge pull request #429 from philips/README-remove-prevValue
fix(README): remove prevValue references in the README
2013-12-30 13:53:17 -08:00
b7be38da12 Merge pull request #430 from philips/remove-old-release-version
chore(release_version): remove this unused file
2013-12-30 13:52:44 -08:00
6b8b6cda66 Merge pull request #431 from btipling/master
Weird grammar, not sure I fixed it well enough.
2013-12-30 13:06:09 -08:00
850cbf6018 Weird grammar, not sure I fixed it well enough. 2013-12-30 12:49:19 -08:00
dcabc6ae80 chore(release_version): remove this unused file
Fixes #425
2013-12-30 11:37:32 -08:00
1ab3db18bb fix(README): remove prevValue references in the README
We plan on adding a PrevNode field in the Response object but it hasn't
been needed thus far. Discussion was here:

http://thread.gmane.org/gmane.comp.distributed.etcd/56

Fixes #428
2013-12-30 10:44:35 -08:00
8edc1f30d9 Merge pull request #424 from xiangli-cmu/fix_lock_doc
docs(README.md) fix wrong index value in lock section
2013-12-29 17:26:26 -08:00
5266968b61 docs(README.md) fix wrong index value in lock section 2013-12-30 09:24:18 +08:00
9bd36f173e Merge pull request #422 from dsoprea/master
Add documentation for enumerating sorted, in-order keys.
2013-12-29 10:09:01 -08:00
a9e20aecc6 Raft fixes, integrate logging. 2013-12-29 10:40:10 -07:00
e66f6d76e6 Modified doc. 2013-12-28 11:38:59 -05:00
c62603ba73 Added documentation for sorted listings. 2013-12-28 11:37:32 -05:00
4692d6e966 Merge pull request #421 from philips/README-api-versioning
feat(README): add an API versioning section
2013-12-28 07:33:13 -08:00
9049a2afd5 feat(README): add an API versioning section
Based on a question on the mailing list. Add a section about API
versioning.
2013-12-28 07:13:41 -08:00
bbbf8fd574 fix(watcher_hub.go) decrease count when remove a watcher 2013-12-28 15:51:16 +08:00
d66dc3c1c7 refactor(watcher_hub.go) refactor notifyWatchers() 2013-12-28 15:49:05 +08:00
59ccefee0f fix(watchhub.go) add a lock to protect the hashmap 2013-12-28 14:55:50 +08:00
3b485e20bb Merge pull request #419 from kula/fix-leader-documentation
fix(README): fixup leader docs
2013-12-27 22:48:31 -08:00
44af8ea190 bump(github.com/coreos/raft): ca61124b291fe38a2ab592a8b42f1423e3c31cc4 2013-12-27 15:24:37 -07:00
2504c4ad34 bump(code.google.com/p/goprotobuf): dd4df9b5c0cf 2013-12-27 15:23:35 -07:00
d447ba52ad fix(README): fixup leader docs
The leader module uses PUT to set a leader value and DELETE to delete one. Fix
the documents to reflect that.
2013-12-27 15:15:19 -05:00
5e499456f0 init cancel watcher 2013-12-26 22:06:15 +08:00
e96234382a Merge pull request #417 from philips/fixup-readme
fix(README): Remove excress backticks
2013-12-25 23:06:25 -08:00
78e2a27ee0 fix(README): Remove excress backticks 2013-12-25 19:28:43 -08:00
715b4d7bfc Merge pull request #408 from xiangli-cmu/compareAndDelete
Compare and delete
2013-12-25 13:16:27 -08:00
c36f306a1d test(delete_handler_test.go) fix inconsistent between test case and comments 2013-12-25 19:05:40 +08:00
bfa7d54b02 refactor(store.go) handle short condition first 2013-12-25 19:01:04 +08:00
4ff773aaaf bump(github.com/coreos/raft): 0c36c972a25343e7e353257284ef6df5c8676a3d 2013-12-23 16:08:10 -07:00
0611c81e88 bump(code.google.com/p/go.net): 127da548775d 2013-12-23 16:02:02 -07:00
6b4f7cf861 bump(github.com/coreos/go-etcd): d2eb551cc057fdaf9848d6130292187bb1368208 2013-12-23 16:01:54 -07:00
51406a3582 bump(github.com/coreos/raft): 0409b22a50cb2576318f294eba5c1092316dbea1 2013-12-23 16:01:53 -07:00
9ebac0b9fd bump(github.com/BurntSushi/toml): da57f3b4c85ec56cf139d7dc05396fa98a040773 2013-12-23 16:01:51 -07:00
4acfc26c5e Add event-based debugging output. 2013-12-23 16:01:05 -07:00
5271378fc2 Merge pull request #416 from drnic/patch-1
[readme] references to BOSH releases using etcd
2013-12-23 11:14:28 -08:00
5ab2f5454c [readme] references to BOSH releases using etcd 2013-12-23 09:49:24 -08:00
4748da1e09 Merge pull request #415 from philips/fix-lock-docs
fix(README): fixup lock docs
2013-12-23 05:53:23 -08:00
c3b4d10b74 fix(README): fixup lock docs
I thought that @benbjohnson had changed the lock "value" to "name" but
that doesn't look to be the case. It is OK, just fix the docs.
2013-12-22 22:03:07 -08:00
f026d1c14e Merge pull request #414 from philips/getting-started
fix(README): we are building containers too
2013-12-22 20:43:19 -08:00
e30bf19684 fix(README): we are building containers too
We are building a docker container now too so don't get specific about
just the binary.

I thought about adding instructions to the README but lets just keep
following the pattern of putting version getting started guides on the
release page.
2013-12-22 20:13:02 -08:00
d4553714d9 Merge pull request #413 from philips/event_history
fix(event_history): fix a bug in event queue
2013-12-22 15:48:31 -08:00
e1d909eb0e test(store/event_test): add a test for a full queue 2013-12-22 15:42:51 -08:00
317b34f4a0 refactor(store/event_history): cleanup some comments 2013-12-22 15:42:10 -08:00
0937b4d266 refactor(event_history.go) remove the extra logic 2013-12-22 15:42:10 -08:00
ef988020b7 fix(event_history) fix a bug in event queue 2013-12-22 15:42:10 -08:00
70c8c09360 Merge pull request #412 from mojotech/cas/403-proper-http-statuses
Use more appropriate HTTP status codes for error cases.
2013-12-21 20:29:40 -08:00
d89fa131ab feat(v2/errors): Use more appropriate HTTP status codes for error cases.
This commits adds test coverage for all the error and non-error cases
described below, but only the behavior of the 403, 404 and 412 cases
are changing in this commit.

When setting a key results in a new resource, we asset an HTTP status
code of 201 (aka "Created").

When attempting to get a resource that doesn't exist, we assert an
HTTP status code of 404 (aka "Not Found").

When attempting to delete a directory without dir=true, or a non-empty
directory without recursive=true, but the request is otherwise valid,
we assert an HTTP status code of 403 (aka "Forbidden").

When a precondition (e.g. specified by prevIndex, or prevValue) is not
met, but the request is otherwise syntactically valid, we assert an
HTTP status code of 412 (aka "Precondition Failed").  However,
prevExist is handled slightly differently.  If prevExist=false fails,
then this is treated like a failed precondition, so it should use
PreconditionFailed.  But, if prevExist=true fails, then this is
treated like other requests that require the existence of the
resource, and uses NotFound if the resource doesn't exist.

We continue to assert an HTTP status code of 400 when the request is
syntactically invalid (e.g. when prevIndex=bad_index).
2013-12-21 21:39:19 -05:00
3f85829e87 fix(v2/tests): make comments and tests agree about what's being tested
In cases where the comments were incorrect, this changes them to agree
with the tests.  In cases where the comments were correct, this extends
the tests to cover the behavior described in the comment.
2013-12-21 21:39:19 -05:00
3cde996d21 refactor(v2/tests): don't repeat construction of full test URL 2013-12-21 21:39:19 -05:00
39fb266776 fix(error.go): fix typo in comment 2013-12-21 21:39:19 -05:00
557ffbb861 Merge pull request #411 from xiangli-cmu/bench
Bench
2013-12-21 14:16:08 -08:00
ddcf3975ed fix bench 2013-12-21 16:44:28 +08:00
cc88215b46 fix(bench): initial commit 2013-12-20 15:19:02 -08:00
3bd2d0da88 bump(code.google.com/p/goprotobuf): 8ba95f2a7335 2013-12-20 15:39:30 -07:00
b7854354ff bump(code.google.com/p/go.net): a1b606ad6242 2013-12-20 15:39:29 -07:00
80bc68eb49 bump(github.com/stretchr/testify/mock): 2013-12-20 15:39:26 -07:00
8c8ab6a4cb bump(github.com/gorilla/mux): 9ede152210fa25c1377d33e867cb828c19316445 2013-12-20 15:39:24 -07:00
985db7838c bump(github.com/gorilla/context): a08edd30ad9e104612741163dc087a613829a23c 2013-12-20 15:39:23 -07:00
04c98d0a75 bump(github.com/coreos/go-systemd): 700560af03f8e1df35839041745c9f1fccba7c72 2013-12-20 15:39:22 -07:00
53436af899 bump(github.com/coreos/go-etcd): 925b981b19b370a2fe073c2395e8f76cfc241124 2013-12-20 15:39:19 -07:00
81a73dbc80 bump(github.com/coreos/raft): ac7be58b1bec49dfcfc7216df4ae27173da1fa57 2013-12-20 15:39:17 -07:00
c4179829d6 tests(get_handler) loosen the time assumption for the un 2013-12-20 08:23:50 +08:00
9cf1fcc987 refactor(compareAndDelete) 2013-12-20 05:10:22 +08:00
e2fa89d554 merge compareAndDelete 2013-12-19 22:19:49 +08:00
75c02ed0da Merge pull request #405 from benbjohnson/tuning
Add Tuning section to README.
2013-12-18 15:42:16 -08:00
c7536ff5e1 Add Tuning section to README. 2013-12-18 16:40:29 -07:00
fd8ce5d11a Merge pull request #398 from benbjohnson/mod-leader
mod/leader
2013-12-16 14:55:11 -08:00
838645f1b7 Merge pull request #402 from philips/add-header-docs
feat(README): document the etcd request headers
2013-12-16 12:44:49 -08:00
4fb8e79237 Merge pull request #404 from philips/add-cetcd
feat(README): add cetcd
2013-12-16 10:38:01 -08:00
c352db9acd feat(README): add cetcd 2013-12-16 10:37:26 -08:00
60813103e3 Merge pull request #401 from philips/add-dir-docs
feat(README): add notes about in-order key creation
2013-12-16 10:30:32 -08:00
d816db07e3 feat(README): add directory tutorial 2013-12-16 10:30:13 -08:00
296eaf7b34 Add leader module to README. 2013-12-16 08:00:16 -07:00
2ce587ebc7 Merge branch 'master' of https://github.com/coreos/etcd into mod-leader 2013-12-16 07:47:31 -07:00
18bf886368 feat(README): document the etcd request headers 2013-12-15 21:45:10 -08:00
1de78fef4d feat(README): add notes about in-order key creation 2013-12-14 13:08:02 -08:00
54794d57fe Merge pull request #400 from philips/add-lock-docs2
fix(README): cleanup lock documentation
2013-12-13 15:07:56 -08:00
412f56f971 fix(README): cleanup lock documentation
accidently merged this without any review. Here are some more cleanups.
2013-12-13 15:07:39 -08:00
e87ee6a86b Merge pull request #399 from philips/add-lock-docs
feat(README): add a modules section to the README
2013-12-13 15:02:37 -08:00
418eccb3d7 feat(README): add a modules section to the README 2013-12-13 15:02:25 -08:00
61227d7477 mod/leader 2013-12-13 15:25:03 -07:00
931ae5fec3 Merge pull request #397 from philips/fixups-for-the-dashboard
Two dashboard fixups
2013-12-13 12:47:00 -08:00
bcf692d775 chore(mod/dashboard): rebuild to the latest version 2013-12-13 12:44:24 -08:00
7e5aa3137d fix(server/registry): use url.Value.Encode()
Instead of open coding url encoding which lead to error, make it real
and use the library.
2013-12-13 12:43:01 -08:00
e24d2fdb6c fix(mod/dashboard): fix after api changes
use node instead of kvs and value
2013-12-13 12:36:29 -08:00
fe80a868a0 Merge pull request #396 from ccding/master
gofmt for ./server ./test
2013-12-12 15:00:41 -08:00
468bfedf34 gofmt 2013-12-12 14:53:22 -08:00
5e1fdf554d Merge pull request #389 from philips/document-ttl-directories
feat(README): add directory TTL documentation
2013-12-12 11:38:49 -08:00
d204fa8438 Merge pull request #394 from xiangli-cmu/fix_error_msg
fix(store.go) report node.path
2013-12-12 10:13:01 -08:00
dba5eb57cf fix(store.go) report node.path 2013-12-12 10:12:33 -08:00
0399593589 Merge pull request #393 from xiangli-cmu/fix_index
fix(dispatch) should call e.Index()
2013-12-12 09:57:16 -08:00
36dda352d9 fix(dispatch) should call e.Index() 2013-12-12 09:56:28 -08:00
44f050f59a Merge pull request #384 from benbjohnson/refactor-mod-lock
Refactor mod/lock.
2013-12-11 19:34:26 -08:00
3caf7745b6 2013-12-11 20:24:43 -07:00
c1508becd6 Merge pull request #390 from xiangli-cmu/fix_v1_index_inconsistence
fix index inconsistence in v1 api
2013-12-11 11:24:53 -08:00
a5bca025b1 fix index inconsistence in v1 api 2013-12-11 11:12:39 -08:00
bb64e7b6e5 feat(README): add directory TTL documentation 2013-12-11 10:47:00 -08:00
f66bd1689d Merge pull request #387 from xiangli-cmu/fix_expire_notify
Fix expire notify
2013-12-11 10:23:53 -08:00
3e4f8a382e fix TestV2WatchKeyInDir test 2013-12-11 10:19:16 -08:00
44b08fea62 Merge branch 'add-expire-dir-test' of https://github.com/philips/etcd into fix_expire_notify 2013-12-11 09:57:34 -08:00
74bd0d95b8 fix(server): try and add a expire dir test
This doesn't actually work yet.
2013-12-10 16:32:37 -08:00
f83e76eb60 Merge https://github.com/coreos/etcd into fix_expire_notify 2013-12-10 15:18:00 -08:00
06473ba6fe fix(store.go) expire should also notify all the watchers under the path 2013-12-10 15:17:13 -08:00
cb9f677cf6 chore(server): cleanup some whitespace 2013-12-10 11:13:37 -08:00
9ea1ef8916 hack(travis): temporarily fix travis
try a quick workaround for https://github.com/travis-ci/travis-ci/issues/1727
2013-12-09 21:46:55 -08:00
dd354c9e22 Merge pull request #376 from xiangli-cmu/dir_flag
feat add dir_flag
2013-12-09 08:34:57 -08:00
59e98fcc62 doc fix grammar issue 2013-12-09 11:33:55 -05:00
4bec461db1 Refactor mod/lock. 2013-12-08 15:26:58 -07:00
8442e7a0dc Timeout refactor. 2013-12-07 14:35:31 -07:00
aabd0faebe Merge branch 'feature-parametric-timeout' of https://github.com/neildunbar/etcd into neildunbar-feature-parametric-timeout 2013-12-07 14:07:53 -07:00
31bc0bb670 Merge pull request #380 from bcwaldon/drop-prevValue
fix(v2): Drop prevValue from exported fields
2013-12-06 14:11:01 -08:00
0fb8fc0b8d fix(v2): Drop prevValue from exported fields 2013-12-06 11:46:23 -08:00
a06f5e74af Merge remote-tracking branch 'upstream/master' into feature-parametric-timeout
Conflicts:
	Dockerfile
	server/usage.go
	tests/server_utils.go
2013-12-06 10:13:33 +00:00
0762c79e2e refactor remove unused const 2013-12-05 21:04:00 -05:00
d646d7c16a tests add tests for dir flag 2013-12-05 20:46:52 -05:00
e00296960c test fix tests 2013-12-05 18:16:01 -05:00
c305eda344 docs(delete_command.go) document about recursive implies dir 2013-12-05 17:52:32 -05:00
636dad190e refactor(error.go) error messages 2013-12-05 17:50:20 -05:00
b556252358 tests fix all tests 2013-12-05 17:48:32 -05:00
4ba7d85d56 refactor(update) more clear dir checking 2013-12-05 17:16:44 -05:00
40d297be66 feat add dir_flag 2013-12-05 17:10:37 -05:00
0867b33de5 fix(Dockerfile): reverted unneeded changes
fix(server/config.go): ensured params are changeable from config file and env
fix(server/server.go): removed unnecessary debug line
fix(server/timeout.go): removed a commented block
style(server/transporter.go): put explicit vars to replace timeout expressions
style(tests/server_utils.go): ran gofmt to clean up indenting
2013-12-05 09:23:23 +00:00
faab194247 Fixed test case 2013-12-04 17:39:03 +00:00
46f8a354d1 Added the ability to specify heartbeat and election timeouts as
config parameters.
2013-12-04 16:58:44 +00:00
3d16633a94 update the v2 server to support recursive on CompareAndDelete events 2013-12-01 13:56:32 -07:00
d2d7e37990 implement recursive for CompareAndDelete in the store 2013-12-01 13:38:09 -07:00
f8985d731f keep the Delete tests together 2013-12-01 13:28:14 -07:00
373199fe46 support CreateAndDelete through the v2 server 2013-11-30 16:25:26 -07:00
171072c736 add the CompareAndDelete command 2013-11-30 16:24:23 -07:00
90a8f56c96 add compareAndDelete event action 2013-11-30 10:08:25 -07:00
5b739f6166 track CompareAndDelete stats 2013-11-30 10:05:48 -07:00
702cf1cc36 teach store.Store about CompareAndDelete 2013-11-30 10:02:03 -07:00
617 changed files with 38365 additions and 89804 deletions

7
.gitignore vendored
View File

@ -1,6 +1,5 @@
src/
pkg/
/etcd
/server/release_version.go
/pkg
/go-bindata
/machine*
/bin
/src

View File

@ -1,8 +0,0 @@
language: go
go: 1.1
install:
- echo "Skip install"
script:
- ./test.sh

View File

@ -1,4 +1,15 @@
v0.2
v0.3.0
* Add Compare-and-Delete support.
* Added prevNode to response objects.
* Added Discovery API.
* Add tracing and debug endpoints (Documentation/debugging.md).
* Improved logging of cluster events.
* go get github.com/coreos/etcd works.
* info file is no longer used.
* Snapshots are on by default.
* Statistics APIs documented.
v0.2.0
* Support directory creation and removal.
* Add Compare-and-Swap (CAS) support.
* Support recursive GETs.

View File

@ -1,10 +1,12 @@
FROM ubuntu:12.04
RUN apt-get update
RUN apt-get install -y python-software-properties git
RUN add-apt-repository -y ppa:duh/golang
RUN apt-get update
RUN apt-get install -y golang
# Let's install go just like Docker (from source).
RUN apt-get update -q
RUN apt-get install -qy build-essential curl git
RUN curl -s https://go.googlecode.com/files/go1.2.src.tar.gz | tar -v -C /usr/local -xz
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
ENV PATH /usr/local/go/bin:$PATH
ADD . /opt/etcd
RUN cd /opt/etcd && ./build
EXPOSE 4001 7001
ENTRYPOINT ["/opt/etcd/etcd"]
ENTRYPOINT ["/opt/etcd/bin/etcd"]

1062
Documentation/api.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
# Client libraries support matrix for etcd
As etcd features support is really uneven between client libraries, a compatibility matrix can be important.
We will consider in detail only the features of clients supporting the v2 API. Clients still supporting the v1 API *only* are listed below.
## v1-only clients
Clients supporting only the API version 1
- [justinsb/jetcd](https://github.com/justinsb/jetcd) Java
- [transitorykris/etcd-py](https://github.com/transitorykris/etcd-py) Python
- [russellhaering/txetcd](https://github.com/russellhaering/txetcd) Python
- [iconara/etcd-rb](https://github.com/iconara/etcd-rb) Ruby
- [jpfuentes2/etcd-ruby](https://github.com/jpfuentes2/etcd-ruby) Ruby
- [aterreno/etcd-clojure](https://github.com/aterreno/etcd-clojure) Clojure
- [marshall-lee/etcd.erl](https://github.com/marshall-lee/etcd.erl) Erlang
## v2 clients
The v2 API has a lot of features, we will categorize them in a few categories:
- **HTTPS Auth**: Support for SSL-certificate based authentication
- **Reconnect**: If the client is able to reconnect automatically to another server if one fails.
- **Mod/Lock**: Support for the locking module
- **Mod/Leader**: Support for the leader election module
- **GET,PUT,POST,DEL Features**: Support for all the modifiers when calling the etcd server with said HTTP method.
### Supported features matrix
| Client| [go-etcd](https://github.com/coreos/go-etcd) | [jetcd](https://github.com/diwakergupta/jetcd) | [python-etcd](https://github.com/jplana/python-etcd) | [python-etcd-client](https://github.com/dsoprea/PythonEtcdClient) | [node-etcd](https://github.com/stianeikeland/node-etcd) | [nodejs-etcd](https://github.com/lavagetto/nodejs-etcd) | [etcd-ruby](https://github.com/ranjib/etcd-ruby) | [etcd-api](https://github.com/jdarcy/etcd-api) | [cetcd](https://github.com/dwwoelfel/cetcd) | [clj-etcd](https://github.com/rthomas/clj-etcd) | [etcetera](https://github.com/drusellers/etcetera)|
| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| **HTTPS Auth** | Y | Y | Y | Y | Y | Y | - | - | - | - | - |
| **Reconnect** | Y | - | Y | Y | - | - | - | Y | - | - | - |
| **Mod/Lock** | Y | - | Y | Y | - | - | - | - | - | - | - |
| **Mod/Leader** | Y | - | - | Y | - | - | - | - | - | - | - |
| **GET Features** | F | B | F | F | F | F | F | B | F | G | F |
| **PUT Features** | F | B | F | F | F | F | F | G | F | G | F |
| **POST Features** | F | - | F | F | - | F | F | - | - | - | F |
| **DEL Features** | F | B | F | F | F | F | F | B | G | B | F |
**Legend**
**F**: Full support **G**: Good support **B**: Basic support
**Y**: Feature supported **-**: Feature not supported

View File

@ -0,0 +1,45 @@
# Cluster Discovery
## Overview
Starting an etcd cluster can be painful since each node needs to know of another node in the cluster to get started. If you are trying to bring up a cluster all at once, say using a cloud formation, you also need to coordinate who will be the initial cluster leader. The discovery protocol helps you by providing an automated way to discover other existing peers in a cluster.
## Using discovery.etcd.io
### Create a Token
To use the discovery API, you must first create a token for your etcd cluster. Visit [https://discovery.etcd.io/new](https://discovery.etcd.io/new) to create a new token.
You can inspect the list of peers by viewing `https://discovery.etcd.io/<token>`.
### Start etcd With the Discovery Flag
Specify the `-discovery` flag when you start each etcd instance. The list of existing peers in the cluster will be downloaded and configured. If the instance is the first peer, it will start as the leader of the cluster.
Here's a full example:
```
TOKEN=$(curl https://discovery.etcd.io/new)
./etcd -name instance1 -peer-addr 10.1.2.3:7001 -addr 10.1.2.3:4001 -discovery https://discovery.etcd.io/$TOKEN
./etcd -name instance2 -peer-addr 10.1.2.4:7002 -addr 10.1.2.4:4002 -discovery https://discovery.etcd.io/$TOKEN
```
## Running Your Own Discovery Endpoint
The discovery API communicates with a separate etcd cluster to store and retrieve the list of peers. CoreOS provides [https://discovery.etcd.io](https://discovery.etcd.io) as a free service, but you can easily run your own etcd cluster for this purpose. Here's an example using an etcd cluster located at `10.10.10.10:4001`:
```
TOKEN="testcluster"
./etcd -name instance1 -peer-addr 10.1.2.3:7001 -addr 10.1.2.3:4001 -discovery http://10.10.10.10:4001/v2/keys/$TOKEN
./etcd -name instance2 -peer-addr 10.1.2.4:7002 -addr 10.1.2.4:4002 -discovery http://10.10.10.10:4001/v2/keys/$TOKEN
```
If you're interested in how to discovery API works behind the scenes, read about the [Discovery Protocol](https://github.com/coreos/etcd/blob/master/Documentation/discovery-protocol.md).
## Setting Peer Addresses Correctly
The Discovery API submits the `-peer-addr` of each etcd instance to the configured Discovery endpoint. It's important to select an address that *all* peers in the cluster can communicate with. For example, if you're located in two regions of a cloud provider, configuring a private `10.x` address will not work between the two regions, and communication will not be possible between all peers.
## Stale Peers
The discovery API will automatically clean up the address of a stale peer that is no longer part of the cluster. The TTL for this process is a week, which should be long enough to handle any extremely long outage you may encounter. There is no harm in having stale peers in the list until they are cleaned up, since an etcd instance only needs to connect to one valid peer in the cluster to join.

174
Documentation/clustering.md Normal file
View File

@ -0,0 +1,174 @@
## Clustering
### Example cluster of three machines
Let's explore the use of etcd clustering.
We use Raft as the underlying distributed protocol which provides consistency and persistence of the data across all of the etcd instances.
Let start by creating 3 new etcd instances.
We use `-peer-addr` to specify server port and `-addr` to specify client port and `-data-dir` to specify the directory to store the log and info of the machine in the cluster:
```sh
./etcd -peer-addr 127.0.0.1:7001 -addr 127.0.0.1:4001 -data-dir machines/machine1 -name machine1
```
**Note:** If you want to run etcd on an external IP address and still have access locally, you'll need to add `-bind-addr 0.0.0.0` so that it will listen on both external and localhost addresses.
A similar argument `-peer-bind-addr` is used to setup the listening address for the server port.
Let's join two more machines to this cluster using the `-peers` argument. A single connection to any peer will allow a new machine to join, but multiple can be specified for greater resiliency.
```sh
./etcd -peer-addr 127.0.0.1:7002 -addr 127.0.0.1:4002 -peers 127.0.0.1:7001,127.0.0.1:7003 -data-dir machines/machine2 -name machine2
./etcd -peer-addr 127.0.0.1:7003 -addr 127.0.0.1:4003 -peers 127.0.0.1:7001,127.0.0.1:7002 -data-dir machines/machine3 -name machine3
```
We can retrieve a list of machines in the cluster using the HTTP API:
```sh
curl -L http://127.0.0.1:4001/v2/machines
```
We should see there are three machines in the cluster
```
http://127.0.0.1:4001, http://127.0.0.1:4002, http://127.0.0.1:4003
```
The machine list is also available via the main key API:
```sh
curl -L http://127.0.0.1:4001/v2/keys/_etcd/machines
```
```json
{
"action": "get",
"node": {
"createdIndex": 1,
"dir": true,
"key": "/_etcd/machines",
"modifiedIndex": 1,
"nodes": [
{
"createdIndex": 1,
"key": "/_etcd/machines/machine1",
"modifiedIndex": 1,
"value": "raft=http://127.0.0.1:7001&etcd=http://127.0.0.1:4001"
},
{
"createdIndex": 2,
"key": "/_etcd/machines/machine2",
"modifiedIndex": 2,
"value": "raft=http://127.0.0.1:7002&etcd=http://127.0.0.1:4002"
},
{
"createdIndex": 3,
"key": "/_etcd/machines/machine3",
"modifiedIndex": 3,
"value": "raft=http://127.0.0.1:7003&etcd=http://127.0.0.1:4003"
}
]
}
}
```
We can also get the current leader in the cluster:
```
curl -L http://127.0.0.1:4001/v2/leader
```
The first server we set up should still be the leader unless it has died during these commands.
```
http://127.0.0.1:7001
```
Now we can do normal SET and GET operations on keys as we explored earlier.
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
```
```json
{
"action": "set",
"node": {
"createdIndex": 4,
"key": "/foo",
"modifiedIndex": 4,
"value": "bar"
}
}
```
### Killing Nodes in the Cluster
Now if we kill the leader of the cluster, we can get the value from one of the other two machines:
```sh
curl -L http://127.0.0.1:4002/v2/keys/foo
```
We can also see that a new leader has been elected:
```
curl -L http://127.0.0.1:4002/v2/leader
```
```
http://127.0.0.1:7002
```
or
```
http://127.0.0.1:7003
```
### Testing Persistence
Next we'll kill all the machines to test persistence.
Type `CTRL-C` on each terminal and then rerun the same command you used to start each machine.
Your request for the `foo` key will return the correct value:
```sh
curl -L http://127.0.0.1:4002/v2/keys/foo
```
```json
{
"action": "get",
"node": {
"createdIndex": 4,
"key": "/foo",
"modifiedIndex": 4,
"value": "bar"
}
}
```
### Using HTTPS between servers
In the previous example we showed how to use SSL client certs for client-to-server communication.
Etcd can also do internal server-to-server communication using SSL client certs.
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.
### What size cluster should I use?
Every command the client sends to the master is broadcast to all of the followers.
The command is not committed until the majority of the cluster peers receive that command.
Because of this majority voting property, the ideal cluster should be kept small to keep speed up and be made up of an odd number of peers.
Odd numbers are good because if you have 8 peers the majority will be 5 and if you have 9 peers the majority will still be 5.
The result is that an 8 peer cluster can tolerate 3 peer failures and a 9 peer cluster can tolerate 4 machine failures.
And in the best case when all 9 peers are responding the cluster will perform at the speed of the fastest 5 machines.

View File

@ -19,6 +19,7 @@ configuration files.
### Optional
* `-addr` - The advertised public hostname:port for client communication. Defaults to `127.0.0.1:4001`.
* `-discovery` - A URL to use for discovering the peer list. (i.e `"https://discovery.etcd.io/your-unique-key"`).
* `-bind-addr` - The listening hostname for client communication. Defaults to advertised ip.
* `-peers` - A comma separated list of peers in the cluster (i.e `"203.0.113.101:7001,203.0.113.102:7001"`).
* `-peers-file` - The file path containing a comma separated list of peers in the cluster.
@ -26,7 +27,7 @@ configuration files.
* `-cert-file` - The cert file of the client.
* `-key-file` - The key file of the client.
* `-config` - The path of the etcd config file. Defaults to `/etc/etcd/etcd.conf`.
* `-cors-origins` - A comma separated white list of origins for cross-origin resource sharing.
* `-cors` - A comma separated white list of origins for cross-origin resource sharing.
* `-cpuprofile` - The path to a file to output cpu profile data. Enables cpu profiling when present.
* `-data-dir` - The directory to store log and snapshot. Defaults to the current working directory.
* `-max-result-buffer` - The max size of result buffer. Defaults to `1024`.
@ -37,11 +38,10 @@ configuration files.
* `-peer-ca-file` - The path of the CAFile. Enables client/peer cert authentication when present.
* `-peer-cert-file` - The cert file of the server.
* `-peer-key-file` - The key file of the server.
* `-snapshot` - Open or close snapshot. Defaults to `false`.
* `-snapshot=false` - Disable log snapshots. Defaults to `true`.
* `-v` - Enable verbose logging. Defaults to `false`.
* `-vv` - Enable very verbose logging. Defaults to `false`.
* `-version` - Print the version and exit.
* `-web-url` - The hostname:port of web interface.
## Configuration File
@ -53,7 +53,7 @@ addr = "127.0.0.1:4001"
bind_addr = "127.0.0.1:4001"
ca_file = ""
cert_file = ""
cors_origins = []
cors = []
cpu_profile_file = ""
data_dir = "."
key_file = ""
@ -66,7 +66,6 @@ name = "default-name"
snapshot = false
verbose = false
very_verbose = false
web_url = ""
[peer]
addr = "127.0.0.1:7001"
@ -96,7 +95,6 @@ key_file = ""
* `ETCD_SNAPSHOT`
* `ETCD_VERBOSE`
* `ETCD_VERY_VERBOSE`
* `ETCD_WEB_URL`
* `ETCD_PEER_ADDR`
* `ETCD_PEER_BIND_ADDR`
* `ETCD_PEER_CA_FILE`

View File

@ -0,0 +1,69 @@
# Debugging etcd
Diagnosing issues in a distributed application is hard.
etcd will help as much as it can - just enable these debug features using the CLI flag `-trace=*` or the config option `trace=*`.
## Logging
Log verbosity can be increased to the max using either the `-vvv` CLI flag or the `very_very_verbose=true` config option.
The only supported logging mode is to stdout.
## Metrics
etcd itself can generate a set of metrics.
These metrics represent many different internal data points that can be helpful when debugging etcd servers.
#### Metrics reference
Each individual metric name is prefixed with `etcd.<NAME>`, where \<NAME\> is the configured name of the etcd server.
* `timer.appendentries.handle`: amount of time a peer takes to process an AppendEntriesRequest from the POV of the peer itself
* `timer.peer.<PEER>.heartbeat`: amount of time a peer heartbeat operation takes from the POV of the leader that initiated that operation for peer \<PEER\>
* `timer.command.<COMMAND>`: amount of time a given command took to be processed through the local server's raft state machine. This does not include time waiting on locks.
#### Fetching metrics over HTTP
Once tracing has been enabled on a given etcd server, all metric data is available at the server's `/debug/metrics` HTTP endpoint (i.e. `http://127.0.0.1:4001/debug/metrics`).
Executing a GET HTTP command against the metrics endpoint will yield the current state of all metrics in the etcd server.
#### Sending metrics to Graphite
etcd supports [Graphite's Carbon plaintext protocol](https://graphite.readthedocs.org/en/latest/feeding-carbon.html#the-plaintext-protocol) - a TCP wire protocol designed for shipping metric data to an aggregator.
To send metrics to a Graphite endpoint using this protocol, use of the `-graphite-host` CLI flag or the `graphite_host` config option (i.e. `graphite_host=172.17.0.19:2003`).
See an [example graphite deploy script](https://github.com/coreos/etcd/contrib/graphite).
#### Generating additional metrics with Collectd
[Collectd](http://collectd.org/documentation.shtml) gathers metrics from the host running etcd.
While these aren't metrics generated by etcd itself, it can be invaluable to compare etcd's view of the world to that of a separate process running next to etcd.
See an [example collectd deploy script](https://github.com/coreos/etcd/contrib/collectd).
## Profiling
etcd exposes profiling information from the Go pprof package over HTTP.
The basic browseable interface is served by etcd at the `/debug/pprof` HTTP endpoint (i.e. `http://127.0.0.1:4001/debug/pprof`).
For more information on using profiling tools, see http://blog.golang.org/profiling-go-programs.
**NOTE**: In the following examples you need to ensure that the `./bin/etcd` is identical to the `./bin/etcd` that you are targetting (same git hash, arch, platform, etc).
#### Heap memory profile
```
go tool pprof ./bin/etcd http://127.0.0.1:4001/debug/pprof/heap
```
#### CPU profile
```
go tool pprof ./bin/etcd http://127.0.0.1:4001/debug/pprof/profile
```
#### Blocked goroutine profile
```
go tool pprof ./bin/etcd http://127.0.0.1:4001/debug/pprof/block
```

View File

@ -0,0 +1,87 @@
# Discovery Protocol
Starting a new etcd cluster can be painful since each machine needs to know of at least one live machine in the cluster. If you are trying to bring up a new cluster all at once, say using an AWS cloud formation, you also need to coordinate who will be the initial cluster leader. The discovery protocol uses an existing running etcd cluster to start a second etcd cluster.
To use this feature you add the command line flag `-discovery` to your etcd args. In this example we will use `http://example.com/v2/keys/_etcd/registry` as the URL prefix.
## The Protocol
By convention the etcd discovery protocol uses the key prefix `_etcd/registry`. A full URL to the keyspace will be `http://example.com/v2/keys/_etcd/registry`.
### Creating a New Cluster
Generate a unique token that will identify the new cluster. This will be used as a key prefix in the following steps. An easy way to do this is to use uuidgen:
```
UUID=$(uuidgen)
```
### Bringing up Machines
Now that you have your cluster ID you can start bringing up machines. Every machine will follow this protocol internally in etcd if given a `-discovery`.
### Registering your Machine
The first thing etcd must do is register your machine. This is done by using the machine name (from the `-name` arg) and posting it with a long TTL to the given key.
```
curl -X PUT "http://example.com/v2/keys/_etcd/registry/${UUID}/${etcd_machine_name}?ttl=604800" -d value=${peer_addr}
```
### Discovering Peers
Now that this etcd machine is registered it must discover its peers.
But, the tricky bit of starting a new cluster is that one machine needs to assume the initial role of leader and will have no peers. To figure out if another machine has already started the cluster etcd needs to create the `_state` key and set its value to "started":
```
curl -X PUT "http://example.com/v2/keys/_etcd/registry/${UUID}/_state?prevExist=false" -d value=started
```
If this returns a `200 OK` response then this machine is the initial leader and should start with no peers configured. If, however, this returns a `412 Precondition Failed` then you need to find all of the registered peers:
```
curl -X GET "http://example.com/v2/keys/_etcd/registry/${UUID}?recursive=true"
```
```
{
"action": "get",
"node": {
"createdIndex": 11,
"dir": true,
"key": "/_etcd/registry/9D4258A5-A1D3-4074-8837-31C1E091131D",
"modifiedIndex": 11,
"nodes": [
{
"createdIndex": 16,
"expiration": "2014-02-03T13:19:57.631253589-08:00",
"key": "/_etcd/registry/9D4258A5-A1D3-4074-8837-31C1E091131D/peer1",
"modifiedIndex": 16,
"ttl": 604765,
"value": "127.0.0.1:7001"
},
{
"createdIndex": 17,
"expiration": "2014-02-03T13:19:57.631253589-08:00",
"key": "/_etcd/registry/9D4258A5-A1D3-4074-8837-31C1E091131D/peer2",
"modifiedIndex": 17,
"ttl": 604765,
"value": "127.0.0.1:7002"
}
]
}
}
```
Using this information you can connect to the rest of the peers in the cluster.
### Heartbeating
At this point etcd will start heart beating to your registration URL. The
protocol uses a heartbeat so permanently deleted nodes get slowly removed from
the discovery information cluster.
The heartbeat interval is about once per day and the TTL is one week. This
should give a sufficiently wide window to protect against a discovery service
taking a temporary outage yet provide adequate cleanup.

View File

@ -5,13 +5,13 @@
![alt text](./img/etcd_fs_structure.jpg "etcd file system structure")
## Node
In **Etcd**, the **Node** is the rudimentary element constructing the whole.
Currently **Etcd** file system is comprised in a Unix-like way of files and directories, and they are two kinds of nodes different in:
In **etcd**, the **node** is the base from which the filesystem is constructed.
**etcd**'s file system is Unix-like with two kinds of nodes: file and directories.
- **File Node** has data associated with it.
- **Directory Node** has children nodes associated with it.
- A **file node** has data associated with it.
- A **directory node** has child nodes associated with it.
Besides the file and directory difference, all nodes have common attributes and operations as follows:
All nodes, regardless of type, have the following attributes and operations:
### Attributes:
- **Expiration Time** [optional]
@ -20,7 +20,7 @@ Besides the file and directory difference, all nodes have common attributes and
- **ACL**
The path of access control list of the node.
The path to the node's access control list.
### Operation:
- **Get** (path, recursive, sorted)
@ -55,7 +55,7 @@ Besides the file and directory difference, all nodes have common attributes and
- **TestAndSet** (path, prevValue [prevIndex], value, ttl)
Atomic *test and set* value to a file. If test succeeds, this operation will change the previous value of the file to the given value.
- If the prevValue is given, it will test against previous value of
- If the prevValue is given, it will test against previous value of
the node.
- If the prevValue is empty, it will test if the node is not existing.
- If the prevValue is not empty, it will test if the prevValue is equal to the current value of the file.
@ -69,7 +69,7 @@ Besides the file and directory difference, all nodes have common attributes and
### Theory
Etcd exports a Unix-like file system interface consisting of files and directories, collectively called nodes.
Each node has various meta-data, including three names of access control lists used to control reading, writing and changing (change ACL names for the node).
Each node has various meta-data, including three names of the access control lists used to control reading, writing and changing (change ACL names for the node).
We are storing the ACL names for nodes under a special *ACL* directory.
Each node has ACL name corresponding to one file within *ACL* dir.

View File

@ -0,0 +1,79 @@
## Libraries and Tools
**Tools**
- [etcdctl](https://github.com/coreos/etcdctl) - A command line client for etcd
- [etcd-dump](https://npmjs.org/package/etcd-dump) - Command line utility for dumping/restoring etcd.
**Go libraries**
- [go-etcd](https://github.com/coreos/go-etcd) - Supports v2
**Java libraries**
- [justinsb/jetcd](https://github.com/justinsb/jetcd)
- [diwakergupta/jetcd](https://github.com/diwakergupta/jetcd) - Supports v2
**Python libraries**
- [transitorykris/etcd-py](https://github.com/transitorykris/etcd-py)
- [jplana/python-etcd](https://github.com/jplana/python-etcd) - Supports v2
- [russellhaering/txetcd](https://github.com/russellhaering/txetcd) - a Twisted Python library
**Node libraries**
- [stianeikeland/node-etcd](https://github.com/stianeikeland/node-etcd) - Supports v2 (w Coffeescript)
- [lavagetto/nodejs-etcd](https://github.com/lavagetto/nodejs-etcd) - Supports v2
**Ruby libraries**
- [iconara/etcd-rb](https://github.com/iconara/etcd-rb)
- [jpfuentes2/etcd-ruby](https://github.com/jpfuentes2/etcd-ruby)
- [ranjib/etcd-ruby](https://github.com/ranjib/etcd-ruby) - Supports v2
**C libraries**
- [jdarcy/etcd-api](https://github.com/jdarcy/etcd-api) - Supports v2
**Clojure libraries**
- [aterreno/etcd-clojure](https://github.com/aterreno/etcd-clojure)
- [dwwoelfel/cetcd](https://github.com/dwwoelfel/cetcd) - Supports v2
- [rthomas/clj-etcd](https://github.com/rthomas/clj-etcd) - Supports v2
**Erlang libraries**
- [marshall-lee/etcd.erl](https://github.com/marshall-lee/etcd.erl)
**.Net Libraries**
-[drusellers/etcetera](https://github.com/drusellers/etcetera)
A detailed recap of client functionalities can be found in the [clients compatibility matrix][clients-matrix.md].
[clients-matrix.md]: https://github.com/coreos/etcd/blob/master/Documentation/clients-matrix.md
**Chef Integration**
- [coderanger/etcd-chef](https://github.com/coderanger/etcd-chef)
**Chef Cookbook**
- [spheromak/etcd-cookbook](https://github.com/spheromak/etcd-cookbook)
**BOSH Releases**
- [cloudfoundry-community/etcd-boshrelease](https://github.com/cloudfoundry-community/etcd-boshrelease)
- [cloudfoundry/cf-release](https://github.com/cloudfoundry/cf-release/tree/master/jobs/etcd)
**Projects using etcd**
- [binocarlos/yoda](https://github.com/binocarlos/yoda) - etcd + ZeroMQ
- [calavera/active-proxy](https://github.com/calavera/active-proxy) - HTTP Proxy configured with etcd
- [derekchiang/etcdplus](https://github.com/derekchiang/etcdplus) - A set of distributed synchronization primitives built upon etcd
- [go-discover](https://github.com/flynn/go-discover) - service discovery in Go
- [gleicon/goreman](https://github.com/gleicon/goreman/tree/etcd) - Branch of the Go Foreman clone with etcd support
- [garethr/hiera-etcd](https://github.com/garethr/hiera-etcd) - Puppet hiera backend using etcd
- [mattn/etcd-vim](https://github.com/mattn/etcd-vim) - SET and GET keys from inside vim
- [mattn/etcdenv](https://github.com/mattn/etcdenv) - "env" shebang with etcd integration
- [kelseyhightower/confd](https://github.com/kelseyhightower/confd) - Manage local app config files using templates and data from etcd
- [configdb](https://git.autistici.org/ai/configdb/tree/master) - A REST relational abstraction on top of arbitrary database backends, aimed at storing configs and inventories.

105
Documentation/modules.md Normal file
View File

@ -0,0 +1,105 @@
## 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/`.
This dashboard is compiled into the etcd binary and uses the same API as regular etcd clients.
Use the `-cors='*'` flag to allow your browser to request information from the current master as it changes.
### 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 value.
The value is unique so if a lock tries to request a value that is already queued for a lock then it will find it and watch until that value obtains the lock.
If you lock the same value 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 value) 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 value "bar"**
```sh
curl -X POST http://127.0.0.1:4001/mod/v2/lock/customer1?ttl=60 -d value=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 value "customer1"**
```sh
curl -X PUT http://127.0.0.1:4001/mod/v2/lock/customer1?ttl=60 -d value=bar
```
**Retrieve the current value 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=2
```
**Delete the "customer1" lock with the value "bar"**
```sh
curl -X DELETE http://127.0.0.1:4001/mod/v2/lock/customer1?value=bar
```
### Leader Election
The Leader Election module wraps the Lock module to allow clients to come to consensus on a single value.
This is useful when you want one server to process at a time but allow other servers to fail over.
The API is similar to the Lock module but is limited to simple strings values.
Here's the API:
**Attempt to set a value for the "order_processing" leader key:**
```sh
curl -X PUT http://127.0.0.1:4001/mod/v2/leader/order_processing?ttl=60 -d name=myserver1.foo.com
```
**Retrieve the current value for the "order_processing" leader key:**
```sh
curl http://127.0.0.1:4001/mod/v2/leader/order_processing
myserver1.foo.com
```
**Remove a value from the "order_processing" leader key:**
```sh
curl -X DELETE http://127.0.0.1:4001/mod/v2/leader/order_processing?name=myserver1.foo.com
```
If multiple clients attempt to set the value for a key then only one will succeed.
The other clients will hang until the current value is removed because of TTL or because of a `DELETE` operation.
Multiple clients can submit the same value and will all be notified when that value succeeds.
To update the TTL of a value simply reissue the same `PUT` command that you used to set the value.

View File

@ -0,0 +1,30 @@
# Optimal etcd Cluster Size
etcd's Raft consensus algorithm is most efficient in small clusters between 3 and 9 peers. Let's briefly explore how etcd works internally to understand why.
## Writing to etcd
Writes to an etcd peer are always redirected to the leader of the cluster and distributed to all of the peers immediately. A write is only considered successful when a majority of the peers acknowledge the write.
For example, in a 5 node cluster, a write operation is only as fast as the 3rd fastest machine. This is the main reason for keeping your etcd cluster below 9 nodes. In practice, you only need to worry about write performance in high latency environments such as a cluster spanning multiple data centers.
## Leader Election
The leader election process is similar to writing a key &mdash; a majority of the cluster must acknowledge the new leader before cluster operations can continue. The longer each node takes to elect a new leader means you have to wait longer before you can write to the cluster again. In low latency environments this process takes milliseconds.
## Odd Cluster Size
The other important cluster optimization is to always have an odd cluster size. Adding an odd node to the cluster doesn't change the size of the majority and therefore doesn't increase the total latency of the majority as described above. But you do gain a higher tolerance for peer failure by adding the extra machine. You can see this in practice when comparing two even and odd sized clusters:
| Cluster Size | Majority | Failure Tolerance |
|--------------|------------|-------------------|
| 8 machines | 5 machines | 3 machines |
| 9 machines | 5 machines | **4 machines** |
As you can see, adding another node to bring the cluster up to an odd size is always worth it. During a network partition, an odd cluster size also guarantees that there will almost always be a majority of the cluster that can continue to operate and be the source of truth when the partition ends.
## Cluster Management
Currently, each CoreOS machine is an etcd peer &mdash; if you have 30 CoreOS machines, you have 30 etcd peers and end up with a cluster size that is way too large. If desired, you may manually stop some of these etcd instances to increase cluster performance.
Functionality is being developed to expose two different types of followers: active and benched followers. Active followers will influence operations within the cluster. Benched followers will not participate, but will transparently proxy etcd traffic to an active follower. This allows every CoreOS machine to expose etcd on port 4001 for ease of use. Benched followers will have the ability to transition into an active follower if needed.

View File

@ -0,0 +1,62 @@
# FreeBSD
Starting with version 0.1.2 both etcd and etcdctl have been ported to FreeBSD and can
be installed either via packages or ports system. Their versions have been recently
updated to 0.2.0 so now you can enjoy using etcd and etcdctl on FreeBSD 10.0 (RC4 as
of now) and 9.x where they have been tested. They might also work when installed from
ports on earlier versions of FreeBSD, but your mileage may vary.
## Installation
### Using pkgng package system
1. If you do not have pkg­ng installed, install it with command `pkg` and answering 'Y'
when asked
2. Update your repository data with `pkg update`
3. Install etcd with `pkg install coreos­etcd coreos­etcdctl`
4. Verify successful installation with `pkg info | grep etcd` and you should get:
```
r@fbsd­10:/ # pkg info | grep etcd
coreos­etcd­0.2.0              Highly­available key value store and service discovery
coreos­etcdctl­0.2.0           Simple commandline client for etcd
r@fbsd­10:/ #
```
5. Youre ready to use etcd and etcdctl! For more information about using pkgng, plese
see: http://www.freebsd.org/doc/handbook/pkgng­intro.html
 
### Using ports system
1. If you do not have ports installed, install with with `portsnap fetch extract` (it
may take some time depending on your hardware and network connection)
2. Build etcd with `cd /usr/ports/devel/etcd && make install clean`, you
will get an option to build and install documentation and etcdctl with it.
3. If you havent install it with etcdctl, and you would like to install it later, you can build it
with `cd /usr/ports/devel/etcdctl && make install clean`
4. Verify successful installation with `pkg info | grep etcd` and you should get:
 
```
r@fbsd­10:/ # pkg info | grep etcd
coreos­etcd­0.2.0              Highly­available key value store and service discovery
coreos­etcdctl­0.2.0           Simple commandline client for etcd
r@fbsd­10:/ #
```
5. Youre ready to use etcd and etcdctl! For more information about using ports system,
please see: https://www.freebsd.org/doc/handbook/ports­using.html
## Issues
If you find any issues with the build/install procedure or youve found a problem that
youve verified is local to FreeBSD version only (for example, by not being able to
reproduce it on any other platform, like OSX or Linux), please sent a
problem report using this page for more
information: http://www.freebsd.org/send­pr.html

130
Documentation/security.md Normal file
View File

@ -0,0 +1,130 @@
# Reading and Writing over HTTPS
## Transport Security with HTTPS
Etcd supports SSL/TLS and client cert authentication for clients to server, as well as server to server communication.
First, you need to have a CA cert `clientCA.crt` and signed key pair `client.crt`, `client.key`.
This site has a good reference for how to generate self-signed key pairs:
http://www.g-loaded.eu/2005/11/10/be-your-own-ca/
For testing you can use the certificates in the `fixtures/ca` directory.
Let's configure etcd to use this keypair:
```sh
./etcd -f -name machine0 -data-dir machine0 -cert-file=./fixtures/ca/server.crt -key-file=./fixtures/ca/server.key.insecure
```
There are a few new options we're using:
* `-f` - forces a new machine configuration, even if an existing configuration is found. (WARNING: data loss!)
* `-cert-file` and `-key-file` specify the location of the cert and key files to be used for for transport layer security between the client and server.
You can now test the configuration using HTTPS:
```sh
curl --cacert ./fixtures/ca/server-chain.pem https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
```
You should be able to see the handshake succeed.
**OSX 10.9+ Users**: curl 7.30.0 on OSX 10.9+ doesn't understand certificates passed in on the command line.
Instead you must import the dummy ca.crt directly into the keychain or add the `-k` flag to curl to ignore errors.
If you want to test without the `-k` flag run `open ./fixtures/ca/ca.crt` and follow the prompts.
Please remove this certificate after you are done testing!
If you know of a workaround let us know.
```
...
SSLv3, TLS handshake, Finished (20):
...
```
And also the response from the etcd server:
```json
{
"action": "set",
"key": "/foo",
"modifiedIndex": 3,
"value": "bar"
}
```
## Authentication with HTTPS Client Certificates
We can also do authentication using CA certs.
The clients will provide their cert to the server and the server will check whether the cert is signed by the CA and decide whether to serve the request.
```sh
./etcd -f -name machine0 -data-dir machine0 -ca-file=./fixtures/ca/ca.crt -cert-file=./fixtures/ca/server.crt -key-file=./fixtures/ca/server.key.insecure
```
```-ca-file``` is the path to the CA cert.
Try the same request to this server:
```sh
curl --cacert ./fixtures/ca/server-chain.pem https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
```
The request should be rejected by the server.
```
...
routines:SSL3_READ_BYTES:sslv3 alert bad certificate
...
```
We need to give the CA signed cert to the server.
```sh
curl --key ./fixtures/ca/server2.key.insecure --cert ./fixtures/ca/server2.crt --cacert ./fixtures/ca/server-chain.pem -L https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
```
You should able to see:
```
...
SSLv3, TLS handshake, CERT verify (15):
...
TLS handshake, Finished (20)
```
And also the response from the server:
```json
{
"action": "set",
"node": {
"createdIndex": 12,
"key": "/foo",
"modifiedIndex": 12,
"value": "bar"
}
}
```
### Why SSLv3 alert handshake failure when using SSL client auth?
The `crypto/tls` package of `golang` checks the key usage of the certificate public key before using it.
To use the certificate public key to do client auth, we need to add `clientAuth` to `Extended Key Usage` when creating the certificate public key.
Here is how to do it:
Add the following section to your openssl.cnf:
```
[ ssl_client ]
...
extendedKeyUsage = clientAuth
...
```
When creating the cert be sure to reference it in the `-extensions` flag:
```
openssl ca -config openssl.cnf -policy policy_anything -extensions ssl_client -out certs/machine.crt -infiles machine.csr
```

93
Documentation/tuning.md Normal file
View File

@ -0,0 +1,93 @@
## Tuning
The default settings in etcd should work well for installations on a local network where the average network latency is low.
However, when using etcd across multiple data centers or over networks with high latency you may need to tweak the heartbeat and election timeout settings.
### Timeouts
The underlying distributed consensus protocol relies on two separate timeouts to ensure that nodes can handoff leadership if one stalls or goes offline.
The first timeout is called the *Heartbeat Timeout*.
This is the frequency with which the leader will notify followers that it is still the leader.
etcd batches commands together for higher throughput so this heartbeat timeout is also a delay for how long it takes for commands to be committed.
By default, etcd uses a `50ms` heartbeat timeout.
The second timeout is the *Election Timeout*.
This timeout is how long a follower node will go without hearing a heartbeat before attempting to become leader itself.
By default, etcd uses a `200ms` election timeout.
Adjusting these values is a trade off.
Lowering the heartbeat timeout will cause individual commands to be committed faster but it will lower the overall throughput of etcd.
If your etcd instances have low utilization then lowering the heartbeat timeout can improve your command response time.
The election timeout should be set based on the heartbeat timeout and your network ping time between nodes.
Election timeouts should be at least 10 times your ping time so it can account for variance in your network.
For example, if the ping time between your nodes is 10ms then you should have at least a 100ms election timeout.
You should also set your election timeout to at least 4 to 5 times your heartbeat timeout to account for variance in leader replication.
For a heartbeat timeout of 50ms you should set your election timeout to at least 200ms - 250ms.
You can override the default values on the command line:
```sh
# Command line arguments:
$ etcd -peer-heartbeat-timeout=100 -peer-election-timeout=500
# Environment variables:
$ ETCD_PEER_HEARTBEAT_TIMEOUT=100 ETCD_PEER_ELECTION_TIMEOUT=500 etcd
```
Or you can set the values within the configuration file:
```toml
[peer]
heartbeat_timeout = 100
election_timeout = 100
```
The values are specified in milliseconds.
### Snapshots
etcd appends all key changes to a log file.
This log grows forever and is a complete linear history of every change made to the keys.
A complete history works well for lightly used clusters but clusters that are heavily used would carry around a large log.
To avoid having a huge log etcd makes periodic snapshots.
These snapshots provide a way for etcd to compact the log by saving the current state of the system and removing old logs.
### Snapshot Tuning
Creating snapshots can be expensive so they're only created after a given number of changes to etcd.
By default, snapshots will be made after every 10,000 changes.
If etcd's memory usage and disk usage are too high, you can lower the snapshot threshold by setting the following on the command line:
```sh
# Command line arguments:
$ etcd -snapshot-count=5000
# Environment variables:
$ ETCD_SNAPSHOT_COUNT=5000 etcd
```
Or you can change the setting in the configuration file:
```toml
snapshot_count = 5000
```
You can also disable snapshotting by adding the following to your command line:
```sh
# Command line arguments:
$ etcd -snapshot false
# Environment variables:
$ ETCD_SNAPSHOT=false etcd
```
You can also enable snapshotting within the configuration file:
```toml
snapshot = false
```

890
README.md
View File

@ -2,7 +2,7 @@
README version 0.2.0
[![Build Status](https://travis-ci.org/coreos/etcd.png)](https://travis-ci.org/coreos/etcd)
[![Build Status](https://drone.io/github.com/coreos/etcd/status.png)](https://drone.io/github.com/coreos/etcd/latest)
A highly-available key value store for shared configuration and service discovery.
etcd is inspired by zookeeper and doozer, with a focus on:
@ -32,7 +32,7 @@ Or feel free to just use curl, as in the examples below.
### Getting etcd
The latest release is available as a binary at [Github][github-release].
The latest release and setup instructions are available at [Github][github-release].
[github-release]: https://github.com/coreos/etcd/releases/
@ -47,886 +47,63 @@ cd etcd
./build
```
This will generate a binary in the base directory called `./etcd`.
This will generate a binary called `./bin/etcd`.
_NOTE_: you need go 1.1+. Please check your installation with
_NOTE_: you need go 1.2+. Please check your installation with
```
go version
```
### Running
### Running a single machine
These examples will use a single machine cluster to show you the basics of the etcd REST API.
Let's start etcd:
First start a single machine cluster of etcd:
```sh
./etcd -data-dir machine0 -name machine0
./bin/etcd
```
This will bring up etcd listening on port 4001 for client communication and on port 7001 for server-to-server communication.
The `-data-dir machine0` argument tells etcd to write machine configuration, logs and snapshots to the `./machine0/` directory.
The `-name machine` tells the rest of the cluster that this machine is named machine0.
Next lets set a single key and then retrieve it:
## Usage
### Setting the value to a key
Lets set the first key-value pair to the datastore.
In this case the key is `/message` and the value is `Hello world`.
```sh
curl -L http://127.0.0.1:4001/v2/keys/message -X PUT -d value="Hello world"
```
```json
{
"action": "set",
"node": {
"createdIndex": 2,
"key": "/message",
"modifiedIndex": 2,
"value": "Hello world"
}
}
```
This response contains four fields.
We will introduce three more fields as we try more commands.
1. The action of the request; we set the value via a `PUT` request, thus the action is `set`.
2. The key of the request; we set `/message` to `Hello world`, so the key field is `/message`.
We use a file system like structure to represent the key-value pairs so each key starts with `/`.
3. The current value of the key; we set the value to`Hello world`.
4. Modified Index is a unique, monotonically incrementing index created for each change to etcd.
Requests that change the index include `set`, `delete`, `update`, `create` and `compareAndSwap`.
Since the `get` and `watch` commands do not change state in the store, they do not change the index.
You may notice that in this example the index is `2` even though it is the first request you sent to the server.
This is because there are internal commands that also change the state like adding and syncing servers.
### Get the value of a key
We can get the value that we just set in `/message` by issuing a `GET` request:
```sh
curl -L http://127.0.0.1:4001/v2/keys/message
```
```json
{
"action": "get",
"node": {
"createdIndex": 2,
"key": "/message",
"modifiedIndex": 2,
"value": "Hello world"
}
}
```
### Changing the value of a key
You can change the value of `/message` from `Hello world` to `Hello etcd` with another `PUT` request to the key:
```sh
curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello etcd"
```
```json
{
"action": "set",
"node": {
"createdIndex": 3,
"key": "/message",
"modifiedIndex": 3,
"prevValue": "Hello world",
"value": "Hello etcd"
}
}
```
Notice that `node.prevValue` is set to the previous value of the key - `Hello world`.
It is useful when you want to atomically set a value to a key and get its old value.
### Deleting a key
You can remove the `/message` key with a `DELETE` request:
```sh
curl -L http://127.0.0.1:4001/v2/keys/message -XDELETE
```
```json
{
"action": "delete",
"node": {
"createdIndex": 3,
"key": "/message",
"modifiedIndex": 4,
"prevValue": "Hello etcd"
}
}
```
### Using key TTL
Keys 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 the key when send a `PUT` request:
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -d ttl=5
```
```json
{
"action": "set",
"node": {
"createdIndex": 5,
"expiration": "2013-12-04T12:01:21.874888581-08:00",
"key": "/foo",
"modifiedIndex": 5,
"ttl": 5,
"value": "bar"
}
}
```
Note the two new fields in response:
1. The `expiration` is the time that this key will expire and be deleted.
2. The `ttl` is the time to live for the key, in seconds.
_NOTE_: Keys can only be expired by a cluster leader so if a machine gets disconnected from the cluster, its keys will not expire until it rejoins.
Now you can try to get the key by sending a `GET` request:
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo
```
If the TTL has expired, the key will be deleted, and you will be returned a 100.
```json
{
"cause": "/foo",
"errorCode": 100,
"index": 6,
"message": "Key Not Found"
}
```
### 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.
In one terminal, we send a get request with `wait=true` :
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo?wait=true
```
Now we are waiting for any changes at path `/foo`.
In another terminal, we set a key `/foo` with value `bar`:
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
```
The first terminal should get the notification and return with the same response as the set request.
```json
{
"action": "set",
"node": {
"createdIndex": 7,
"key": "/foo",
"modifiedIndex": 7,
"value": "bar"
}
}
```
However, the watch command can do more than this.
Using the the index we can watch for commands that has happened in the past.
This is useful for ensuring you don't miss events between watch commands.
Let's try to watch for the set command of index 7 again:
```sh
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.
### 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.
This command will set the value of a key only if the client-provided conditions are equal to the current conditions.
The current comparable conditions are:
1. `prevValue` - checks the previous value of the key.
2. `prevIndex` - checks the previous index of the key.
3. `prevExist` - checks existence of the key: if `prevExist` is true, it is a `update` request; if prevExist is `false`, it is a `create` request.
Here is a simple example.
Let's create a key-value pair first: `foo=one`.
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=one
```
Let's try some invalid `CompareAndSwap` commands first.
Trying to set this existing key with `prevExist=false` fails as expected:
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo?prevExist=false -XPUT -d value=three
```
The error code explains the problem:
```json
{
"cause": "/foo",
"errorCode": 105,
"index": 39776,
"message": "Already exists"
}
```
Now lets provide a `prevValue` parameter:
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo?prevValue=two -XPUT -d value=three
```
This will try to compare the previous value of the key and the previous value we provided. If they are equal, the value of the key will change to three.
```json
{
"cause": "[two != one] [0 != 8]",
"errorCode": 101,
"index": 8,
"message": "Test Failed"
}
```
which means `CompareAndSwap` failed.
Let's try a valid condition:
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo?prevValue=one -XPUT -d value=two
```
The response should be
```json
{
"action": "compareAndSwap",
"node": {
"createdIndex": 8,
"key": "/foo",
"modifiedIndex": 9,
"prevValue": "one",
"value": "two"
}
}
```
We successfully changed the value from "one" to "two" since we gave the correct previous value.
### Listing a directory
In etcd we can store two types of things: keys and directories.
Keys store a single string value.
Directories store a set of keys and/or other directories.
In this example, let's first create some keys:
We already have `/foo=two` so now we'll create another one called `/foo_dir/foo` with the value of `bar`:
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo_dir/foo -XPUT -d value=bar
```
```json
{
"action": "set",
"node": {
"createdIndex": 2,
"key": "/foo_dir/foo",
"modifiedIndex": 2,
"value": "bar"
}
}
```
Now we can list the keys under root `/`:
```sh
curl -L http://127.0.0.1:4001/v2/keys/
```
We should see the response as an array of items:
```json
{
"action": "get",
"node": {
"dir": true,
"key": "/",
"nodes": [
{
"createdIndex": 2,
"dir": true,
"key": "/foo_dir",
"modifiedIndex": 2
}
]
}
}
```
Here we can see `/foo` is a key-value pair under `/` and `/foo_dir` is a directory.
We can also recursively get all the contents under a directory by adding `recursive=true`.
```sh
curl -L http://127.0.0.1:4001/v2/keys/?recursive=true
```
```json
{
"action": "get",
"node": {
"dir": true,
"key": "/",
"nodes": [
{
"createdIndex": 2,
"dir": true,
"key": "/foo_dir",
"modifiedIndex": 2,
"nodes": [
{
"createdIndex": 2,
"key": "/foo_dir/foo",
"modifiedIndex": 2,
"value": "bar"
}
]
}
]
}
}
```
### Deleting a directory
Now let's try to delete the directory `/foo_dir`.
To delete a directory, we must add `recursive=true`.
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo_dir?recursive=true -XDELETE
```
```json
{
"action": "delete",
"node": {
"createdIndex": 10,
"dir": true,
"key": "/foo_dir",
"modifiedIndex": 11
}
}
```
### Creating a hidden node
We can create a hidden key-value pair or directory by add a `_` prefix.
The hidden item will not be listed when sending a `GET` request for a directory.
First we'll add a hidden key named `/_message`:
```sh
curl -L http://127.0.0.1:4001/v2/keys/_message -XPUT -d value="Hello hidden world"
```
```json
{
"action": "set",
"node": {
"createdIndex": 3,
"key": "/_message",
"modifiedIndex": 3,
"value": "Hello hidden world"
}
}
```
Next we'll add a regular key named `/message`:
```sh
curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello world"
```
```json
{
"action": "set",
"node": {
"createdIndex": 4,
"key": "/message",
"modifiedIndex": 4,
"value": "Hello world"
}
}
```
Now let's try to get a listing of keys under the root directory, `/`:
```sh
curl -L http://127.0.0.1:4001/v2/keys/
```
```json
{
"action": "get",
"node": {
"dir": true,
"key": "/",
"nodes": [
{
"createdIndex": 2,
"dir": true,
"key": "/foo_dir",
"modifiedIndex": 2
},
{
"createdIndex": 4,
"key": "/message",
"modifiedIndex": 4,
"value": "Hello world"
}
]
}
}
```
Here we see the `/message` key but our hidden `/_message` key is not returned.
## Advanced Usage
### Transport security with HTTPS
Etcd supports SSL/TLS and client cert authentication for clients to server, as well as server to server communication.
First, you need to have a CA cert `clientCA.crt` and signed key pair `client.crt`, `client.key`.
This site has a good reference for how to generate self-signed key pairs:
http://www.g-loaded.eu/2005/11/10/be-your-own-ca/
For testing you can use the certificates in the `fixtures/ca` directory.
Let's configure etcd to use this keypair:
```sh
./etcd -f -name machine0 -data-dir machine0 -cert-file=./fixtures/ca/server.crt -key-file=./fixtures/ca/server.key.insecure
```
There are a few new options we're using:
* `-f` - forces a new machine configuration, even if an existing configuration is found. (WARNING: data loss!)
* `-cert-file` and `-key-file` specify the location of the cert and key files to be used for for transport layer security between the client and server.
You can now test the configuration using HTTPS:
```sh
curl --cacert ./fixtures/ca/server-chain.pem https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
```
You should be able to see the handshake succeed.
**OSX 10.9+ Users**: curl 7.30.0 on OSX 10.9+ doesn't understand certificates passed in on the command line.
Instead you must import the dummy ca.crt directly into the keychain or add the `-k` flag to curl to ignore errors.
If you want to test without the `-k` flag run `open ./fixtures/ca/ca.crt` and follow the prompts.
Please remove this certificate after you are done testing!
If you know of a workaround let us know.
```
...
SSLv3, TLS handshake, Finished (20):
...
```
And also the response from the etcd server:
```json
{
"action": "set",
"key": "/foo",
"modifiedIndex": 3,
"prevValue": "bar",
"value": "bar"
}
```
### Authentication with HTTPS client certificates
We can also do authentication using CA certs.
The clients will provide their cert to the server and the server will check whether the cert is signed by the CA and decide whether to serve the request.
```sh
./etcd -f -name machine0 -data-dir machine0 -ca-file=./fixtures/ca/ca.crt -cert-file=./fixtures/ca/server.crt -key-file=./fixtures/ca/server.key.insecure
```
```-ca-file``` is the path to the CA cert.
Try the same request to this server:
```sh
curl --cacert ./fixtures/ca/server-chain.pem https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
```
The request should be rejected by the server.
```
...
routines:SSL3_READ_BYTES:sslv3 alert bad certificate
...
```
We need to give the CA signed cert to the server.
```sh
curl --key ./fixtures/ca/server2.key.insecure --cert ./fixtures/ca/server2.crt --cacert ./fixtures/ca/server-chain.pem -L https://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar -v
```
You should able to see:
```
...
SSLv3, TLS handshake, CERT verify (15):
...
TLS handshake, Finished (20)
```
And also the response from the server:
```json
{
"action": "set",
"node": {
"createdIndex": 12,
"key": "/foo",
"modifiedIndex": 12,
"prevValue": "two",
"value": "bar"
}
}
```
## Clustering
### Example cluster of three machines
Let's explore the use of etcd clustering.
We use Raft as the underlying distributed protocol which provides consistency and persistence of the data across all of the etcd instances.
Let start by creating 3 new etcd instances.
We use `-peer-addr` to specify server port and `-addr` to specify client port and `-data-dir` to specify the directory to store the log and info of the machine in the cluster:
```sh
./etcd -peer-addr 127.0.0.1:7001 -addr 127.0.0.1:4001 -data-dir machines/machine1 -name machine1
```
**Note:** If you want to run etcd on an external IP address and still have access locally, you'll need to add `-bind-addr 0.0.0.0` so that it will listen on both external and localhost addresses.
A similar argument `-peer-bind-addr` is used to setup the listening address for the server port.
Let's join two more machines to this cluster using the `-peers` argument:
```sh
./etcd -peer-addr 127.0.0.1:7002 -addr 127.0.0.1:4002 -peers 127.0.0.1:7001 -data-dir machines/machine2 -name machine2
./etcd -peer-addr 127.0.0.1:7003 -addr 127.0.0.1:4003 -peers 127.0.0.1:7001 -data-dir machines/machine3 -name machine3
```
We can retrieve a list of machines in the cluster using the HTTP API:
```sh
curl -L http://127.0.0.1:4001/v2/machines
```
We should see there are three machines in the cluster
```
http://127.0.0.1:4001, http://127.0.0.1:4002, http://127.0.0.1:4003
```
The machine list is also available via the main key API:
```sh
curl -L http://127.0.0.1:4001/v2/keys/_etcd/machines
```
```json
{
"action": "get",
"node": {
"createdIndex": 1,
"dir": true,
"key": "/_etcd/machines",
"modifiedIndex": 1,
"nodes": [
{
"createdIndex": 1,
"key": "/_etcd/machines/machine1",
"modifiedIndex": 1,
"value": "raft=http://127.0.0.1:7001&etcd=http://127.0.0.1:4001"
},
{
"createdIndex": 2,
"key": "/_etcd/machines/machine2",
"modifiedIndex": 2,
"value": "raft=http://127.0.0.1:7002&etcd=http://127.0.0.1:4002"
},
{
"createdIndex": 3,
"key": "/_etcd/machines/machine3",
"modifiedIndex": 3,
"value": "raft=http://127.0.0.1:7003&etcd=http://127.0.0.1:4003"
}
]
}
}
```
We can also get the current leader in the cluster:
```
curl -L http://127.0.0.1:4001/v2/leader
```
The first server we set up should still be the leader unless it has died during these commands.
curl -L http://127.0.0.1:4001/v2/keys/mykey -XPUT -d value="this is awesome"
curl -L http://127.0.0.1:4001/v2/keys/mykey
```
http://127.0.0.1:7001
```
Now we can do normal SET and GET operations on keys as we explored earlier.
```sh
curl -L http://127.0.0.1:4001/v2/keys/foo -XPUT -d value=bar
```
```json
{
"action": "set",
"node": {
"createdIndex": 4,
"key": "/foo",
"modifiedIndex": 4,
"value": "bar"
}
}
```
### Killing Nodes in the Cluster
Now if we kill the leader of the cluster, we can get the value from one of the other two machines:
```sh
curl -L http://127.0.0.1:4002/v2/keys/foo
```
We can also see that a new leader has been elected:
```
curl -L http://127.0.0.1:4002/v2/leader
```
```
http://127.0.0.1:7002
```
or
```
http://127.0.0.1:7003
```
### Testing Persistence
Next we'll kill all the machines to test persistence.
Type `CTRL-C` on each terminal and then rerun the same command you used to start each machine.
Your request for the `foo` key will return the correct value:
```sh
curl -L http://127.0.0.1:4002/v2/keys/foo
```
```json
{
"action": "get",
"node": {
"createdIndex": 4,
"key": "/foo",
"modifiedIndex": 4,
"value": "bar"
}
}
```
### Using HTTPS between servers
You have successfully started an etcd on a single machine and written a key to the store. Now it time to dig into the full etcd API and other guides.
In the previous example we showed how to use SSL client certs for client-to-server communication.
Etcd can also do internal server-to-server communication using SSL client certs.
To do this just change the `-*-file` flags to `-peer-*-file`.
### Next Steps
If you are using SSL for server-to-server communication, you must use it on all instances of etcd.
- Explore the full [API][api.md].
- Setup a [multi-machine cluster][clustering.md].
- Learn the [config format, env variables and flags][configuration.md].
- Find [language bindings and tools][libraries-and-tools.md].
- Learn about the dashboard, lock and leader election [modules][modules.md].
- Use TLS to [secure an etcd cluster][security.md].
- [Tune etcd][tuning.md].
[api.md]: https://github.com/coreos/etcd/blob/master/Documentation/api.md
[clustering.md]: https://github.com/coreos/etcd/blob/master/Documentation/clustering.md
[configuration.md]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
[libraries-and-tools.md]: https://github.com/coreos/etcd/blob/master/Documentation/libraries-and-tools.md
[modules.md]: https://github.com/coreos/etcd/blob/master/Documentation/modules.md
[security.md]: https://github.com/coreos/etcd/blob/master/Documentation/security.md
[tuning.md]: https://github.com/coreos/etcd/blob/master/Documentation/tuning.md
## Contributing
See [CONTRIBUTING](https://github.com/coreos/etcd/blob/master/CONTRIBUTING.md) for details on submitting patches and contacting developers via IRC and mailing lists.
## Libraries and Tools
**Tools**
- [etcdctl](https://github.com/coreos/etcdctl) - A command line client for etcd
**Go libraries**
- [go-etcd](https://github.com/coreos/go-etcd)
**Java libraries**
- [justinsb/jetcd](https://github.com/justinsb/jetcd)
- [diwakergupta/jetcd](https://github.com/diwakergupta/jetcd)
**Python libraries**
- [transitorykris/etcd-py](https://github.com/transitorykris/etcd-py)
- [jplana/python-etcd](https://github.com/jplana/python-etcd)
- [russellhaering/txetcd](https://github.com/russellhaering/txetcd) - a Twisted Python library
**Node libraries**
- [stianeikeland/node-etcd](https://github.com/stianeikeland/node-etcd)
**Ruby libraries**
- [iconara/etcd-rb](https://github.com/iconara/etcd-rb)
- [jpfuentes2/etcd-ruby](https://github.com/jpfuentes2/etcd-ruby)
- [ranjib/etcd-ruby](https://github.com/ranjib/etcd-ruby)
**C libraries**
- [jdarcy/etcd-api](https://github.com/jdarcy/etcd-api)
**Clojure libraries**
- [aterreno/etcd-clojure](https://github.com/aterreno/etcd-clojure)
- [rthomas/clj-etcd](https://github.com/rthomas/clj-etcd)
**Erlang libraries**
- [marshall-lee/etcd.erl](https://github.com/marshall-lee/etcd.erl)
**Chef Integration**
- [coderanger/etcd-chef](https://github.com/coderanger/etcd-chef)
**Chef Cookbook**
- [spheromak/etcd-cookbook](https://github.com/spheromak/etcd-cookbook)
**Projects using etcd**
- [binocarlos/yoda](https://github.com/binocarlos/yoda) - etcd + ZeroMQ
- [calavera/active-proxy](https://github.com/calavera/active-proxy) - HTTP Proxy configured with etcd
- [derekchiang/etcdplus](https://github.com/derekchiang/etcdplus) - A set of distributed synchronization primitives built upon etcd
- [go-discover](https://github.com/flynn/go-discover) - service discovery in Go
- [gleicon/goreman](https://github.com/gleicon/goreman/tree/etcd) - Branch of the Go Foreman clone with etcd support
- [garethr/hiera-etcd](https://github.com/garethr/hiera-etcd) - Puppet hiera backend using etcd
- [mattn/etcd-vim](https://github.com/mattn/etcd-vim) - SET and GET keys from inside vim
- [mattn/etcdenv](https://github.com/mattn/etcdenv) - "env" shebang with etcd integration
- [kelseyhightower/confd](https://github.com/kelseyhightower/confd) - Manage local app config files using templates and data from etcd
## FAQ
### What size cluster should I use?
Every command the client sends to the master is broadcast to all of the followers.
The command is not committed until the majority of the cluster peers receive that command.
Because of this majority voting property, the ideal cluster should be kept small to keep speed up and be made up of an odd number of peers.
Odd numbers are good because if you have 8 peers the majority will be 5 and if you have 9 peers the majority will still be 5.
The result is that an 8 peer cluster can tolerate 3 peer failures and a 9 peer cluster can tolerate 4 machine failures.
And in the best case when all 9 peers are responding the cluster will perform at the speed of the fastest 5 machines.
### Why SSLv3 alert handshake failure when using SSL client auth?
The `crypto/tls` package of `golang` checks the key usage of the certificate public key before using it.
To use the certificate public key to do client auth, we need to add `clientAuth` to `Extended Key Usage` when creating the certificate public key.
Here is how to do it:
Add the following section to your openssl.cnf:
```
[ ssl_client ]
...
extendedKeyUsage = clientAuth
...
```
When creating the cert be sure to reference it in the `-extensions` flag:
```
openssl ca -config openssl.cnf -policy policy_anything -extensions ssl_client -out certs/machine.crt -infiles machine.csr
```
## Project Details
### Versioning
#### Service Versioning
etcd uses [semantic versioning][semver].
New minor versions may add additional features to the API however.
New minor versions may add additional features to the API.
You can get the version of etcd by issuing a request to /version:
@ -934,10 +111,15 @@ You can get the version of etcd by issuing a request to /version:
curl -L http://127.0.0.1:4001/version
```
During the pre-v1.0.0 series of releases we may break the API as we fix bugs and get feedback.
[semver]: http://semver.org/
#### API Versioning
Clients are encouraged to use the `v2` API. The `v1` API will not change.
The `v2` API responses should not change after the 0.2.0 release but new features will be added over time.
During the pre-v1.0.0 series of releases we may break the API as we fix bugs and get feedback.
### License

63
bench/bench.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"flag"
"log"
"strconv"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
func write(endpoint string, requests int, end chan int) {
client := etcd.NewClient([]string{endpoint})
for i := 0; i < requests; i++ {
key := strconv.Itoa(i)
_, err := client.Set(key, key, 0)
if err != nil {
println(err.Error())
}
}
end <- 1
}
func watch(endpoint string, key string) {
client := etcd.NewClient([]string{endpoint})
receiver := make(chan *etcd.Response)
go client.Watch(key, 0, true, receiver, nil)
log.Printf("watching: %s", key)
received := 0
for {
<-receiver
received++
}
}
func main() {
endpoint := flag.String("endpoint", "http://127.0.0.1:4001", "etcd HTTP endpoint")
rWrites := flag.Int("write-requests", 50000, "number of writes")
cWrites := flag.Int("concurrent-writes", 500, "number of concurrent writes")
watches := flag.Int("watches", 500, "number of writes")
flag.Parse()
for i := 0; i < *watches; i++ {
key := strconv.Itoa(i)
go watch(*endpoint, key)
}
wChan := make(chan int, *cWrites)
for i := 0; i < *cWrites; i++ {
go write(*endpoint, (*rWrites / *cWrites), wChan)
}
for i := 0; i < *cWrites; i++ {
<-wChan
log.Printf("Completed %d writes", (*rWrites / *cWrites))
}
}

30
build
View File

@ -1,26 +1,12 @@
#!/bin/sh
set -e
#!/bin/sh -e
ETCD_PACKAGE=github.com/coreos/etcd
export GOPATH="${PWD}"
SRC_DIR="$GOPATH/src"
ETCD_DIR="$SRC_DIR/$ETCD_PACKAGE"
ETCD_BASE=$(dirname "${ETCD_DIR}")
if [ ! -d "${ETCD_BASE}" ]; then
mkdir -p "${ETCD_BASE}"
if [ ! -h src/github.com/coreos/etcd ]; then
mkdir -p src/github.com/coreos/
ln -s ../../.. src/github.com/coreos/etcd
fi
if [ ! -h "${ETCD_DIR}" ]; then
ln -s ../../../ "${ETCD_DIR}"
fi
export GOBIN=${PWD}/bin
export GOPATH=${PWD}
for i in third_party/*; do
if [ "$i" = "third_party/src" ]; then
continue
fi
cp -R "$i" src/
done
./scripts/release-version > server/release_version.go
go build "${ETCD_PACKAGE}"
go install github.com/coreos/etcd
go install github.com/coreos/etcd/bench

View File

@ -1,24 +0,0 @@
$ETCD_PACKAGE="github.com/coreos/etcd"
$env:GOPATH=$pwd.Path
$SRC_DIR="$env:GOPATH/src"
$ETCD_DIR="$SRC_DIR/$ETCD_PACKAGE"
$env:ETCD_DIR="$SRC_DIR/$ETCD_PACKAGE"
$ETCD_BASE=(Split-Path $ETCD_DIR -Parent)
if(-not(test-path $ETCD_DIR)){
mkdir -force "$ETCD_BASE" > $null
}
if(-not(test-path $ETCD_DIR )){
cmd /c 'mklink /D "%ETCD_DIR%" ..\..\..\'
}
foreach($i in (ls third_party/*)){
if("$i" -eq "third_party/src") {continue}
cp -Recurse -force "$i" src/
}
./scripts/release-version.ps1 | Out-File -Encoding UTF8 server/release_version.go
go build -v "${ETCD_PACKAGE}"

View File

@ -1,7 +1,6 @@
package server
package config
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
@ -13,8 +12,12 @@ import (
"strconv"
"strings"
"github.com/coreos/etcd/third_party/github.com/BurntSushi/toml"
"github.com/coreos/etcd/discovery"
"github.com/coreos/etcd/log"
"github.com/BurntSushi/toml"
ustrings "github.com/coreos/etcd/pkg/strings"
"github.com/coreos/etcd/server"
)
// The default location for the etcd configuration file.
@ -53,6 +56,7 @@ type Config struct {
CPUProfileFile string
CorsOrigins []string `toml:"cors" env:"ETCD_CORS"`
DataDir string `toml:"data_dir" env:"ETCD_DATA_DIR"`
Discovery string `toml:"discovery" env:"ETCD_DISCOVERY"`
Force bool
KeyFile string `toml:"key_file" env:"ETCD_KEY_FILE"`
Peers []string `toml:"peers" env:"ETCD_PEERS"`
@ -60,6 +64,7 @@ type Config struct {
MaxClusterSize int `toml:"max_cluster_size" env:"ETCD_MAX_CLUSTER_SIZE"`
MaxResultBuffer int `toml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
MaxRetryAttempts int `toml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
RetryInterval float64 `toml:"retry_interval" env:"ETCD_RETRY_INTERVAL"`
Name string `toml:"name" env:"ETCD_NAME"`
Snapshot bool `toml:"snapshot" env:"ETCD_SNAPSHOT"`
SnapshotCount int `toml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`
@ -67,26 +72,34 @@ type Config struct {
ShowVersion bool
Verbose bool `toml:"verbose" env:"ETCD_VERBOSE"`
VeryVerbose bool `toml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
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"`
CertFile string `toml:"cert_file" env:"ETCD_PEER_CERT_FILE"`
KeyFile string `toml:"key_file" env:"ETCD_PEER_KEY_FILE"`
VeryVeryVerbose bool `toml:"very_very_verbose" env:"ETCD_VERY_VERY_VERBOSE"`
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"`
CertFile string `toml:"cert_file" env:"ETCD_PEER_CERT_FILE"`
KeyFile string `toml:"key_file" env:"ETCD_PEER_KEY_FILE"`
HeartbeatTimeout int `toml:"heartbeat_timeout" env:"ETCD_PEER_HEARTBEAT_TIMEOUT"`
ElectionTimeout int `toml:"election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"`
}
strTrace string `toml:"trace" env:"ETCD_TRACE"`
GraphiteHost string `toml:"graphite_host" env:"ETCD_GRAPHITE_HOST"`
}
// NewConfig returns a Config initialized with default values.
func NewConfig() *Config {
// New returns a Config initialized with default values.
func New() *Config {
c := new(Config)
c.SystemPath = DefaultSystemConfigPath
c.Addr = "127.0.0.1:4001"
c.MaxClusterSize = 9
c.MaxResultBuffer = 1024
c.MaxRetryAttempts = 3
c.Peer.Addr = "127.0.0.1:7001"
c.RetryInterval = 10.0
c.Snapshot = true
c.SnapshotCount = 10000
c.Peer.Addr = "127.0.0.1:7001"
c.Peer.HeartbeatTimeout = defaultHeartbeatTimeout
c.Peer.ElectionTimeout = defaultElectionTimeout
return c
}
@ -131,6 +144,13 @@ func (c *Config) Load(arguments []string) error {
return fmt.Errorf("sanitize: %v", err)
}
// Attempt cluster discovery
if c.Discovery != "" {
if err := c.handleDiscovery(); err != nil {
return err
}
}
// Force remove server configuration if specified.
if c.Force {
c.Reset()
@ -189,12 +209,42 @@ func (c *Config) loadEnv(target interface{}) error {
case reflect.String:
value.Field(i).SetString(v)
case reflect.Slice:
value.Field(i).Set(reflect.ValueOf(trimsplit(v, ",")))
value.Field(i).Set(reflect.ValueOf(ustrings.TrimSplit(v, ",")))
}
}
return nil
}
func (c *Config) handleDiscovery() error {
p, err := discovery.Do(c.Discovery, c.Name, c.Peer.Addr)
// This is fatal, discovery encountered an unexpected error
// and we have no peer list.
if err != nil && len(c.Peers) == 0 {
log.Fatalf("Discovery failed and a backup peer list wasn't provided: %v", err)
return err
}
// Warn about errors coming from discovery, this isn't fatal
// since the user might have provided a peer list elsewhere.
if err != nil {
log.Warnf("Discovery encountered an error but a backup peer list (%v) was provided: %v", c.Peers, err)
}
for i := range p {
// Strip the scheme off of the peer if it has one
// TODO(bp): clean this up!
purl, err := url.Parse(p[i])
if err == nil {
p[i] = purl.Host
}
}
c.Peers = p
return nil
}
// Loads configuration from command line flags.
func (c *Config) LoadFlags(arguments []string) error {
var peers, cors, path string
@ -210,13 +260,15 @@ func (c *Config) LoadFlags(arguments []string) error {
f.BoolVar(&c.Force, "force", false, "")
f.BoolVar(&c.Verbose, "v", c.Verbose, "")
f.BoolVar(&c.VeryVerbose, "vv", c.Verbose, "")
f.BoolVar(&c.VeryVerbose, "vv", c.VeryVerbose, "")
f.BoolVar(&c.VeryVeryVerbose, "vvv", c.VeryVeryVerbose, "")
f.StringVar(&peers, "peers", "", "")
f.StringVar(&c.PeersFile, "peers-file", c.PeersFile, "")
f.StringVar(&c.Name, "name", c.Name, "")
f.StringVar(&c.Addr, "addr", c.Addr, "")
f.StringVar(&c.Discovery, "discovery", c.Discovery, "")
f.StringVar(&c.BindAddr, "bind-addr", c.BindAddr, "")
f.StringVar(&c.Peer.Addr, "peer-addr", c.Peer.Addr, "")
f.StringVar(&c.Peer.BindAddr, "peer-bind-addr", c.Peer.BindAddr, "")
@ -232,13 +284,20 @@ func (c *Config) LoadFlags(arguments []string) error {
f.StringVar(&c.DataDir, "data-dir", c.DataDir, "")
f.IntVar(&c.MaxResultBuffer, "max-result-buffer", c.MaxResultBuffer, "")
f.IntVar(&c.MaxRetryAttempts, "max-retry-attempts", c.MaxRetryAttempts, "")
f.Float64Var(&c.RetryInterval, "retry-interval", c.RetryInterval, "")
f.IntVar(&c.MaxClusterSize, "max-cluster-size", c.MaxClusterSize, "")
f.IntVar(&c.Peer.HeartbeatTimeout, "peer-heartbeat-timeout", c.Peer.HeartbeatTimeout, "")
f.IntVar(&c.Peer.ElectionTimeout, "peer-election-timeout", c.Peer.ElectionTimeout, "")
f.StringVar(&cors, "cors", "", "")
f.BoolVar(&c.Snapshot, "snapshot", c.Snapshot, "")
f.IntVar(&c.SnapshotCount, "snapshot-count", c.SnapshotCount, "")
f.StringVar(&c.CPUProfileFile, "cpuprofile", "", "")
f.StringVar(&c.strTrace, "trace", "", "")
f.StringVar(&c.GraphiteHost, "graphite-host", "", "")
// BEGIN IGNORED FLAGS
f.StringVar(&path, "config", "", "")
// BEGIN IGNORED FLAGS
@ -271,16 +330,16 @@ func (c *Config) LoadFlags(arguments []string) error {
// Print deprecation warnings on STDERR.
f.Visit(func(f *flag.Flag) {
if len(newFlagNameLookup[f.Name]) > 0 {
fmt.Fprintf(os.Stderr, "[deprecated] use -%s, not -%s", newFlagNameLookup[f.Name], f.Name)
fmt.Fprintf(os.Stderr, "[deprecated] use -%s, not -%s\n", newFlagNameLookup[f.Name], f.Name)
}
})
// Convert some parameters to lists.
if peers != "" {
c.Peers = trimsplit(peers, ",")
c.Peers = ustrings.TrimSplit(peers, ",")
}
if cors != "" {
c.CorsOrigins = trimsplit(cors, ",")
c.CorsOrigins = ustrings.TrimSplit(cors, ",")
}
return nil
@ -296,7 +355,7 @@ func (c *Config) LoadPeersFile() error {
if err != nil {
return fmt.Errorf("Peers file error: %s", err)
}
c.Peers = trimsplit(string(b), ",")
c.Peers = ustrings.TrimSplit(string(b), ",")
return nil
}
@ -322,9 +381,6 @@ func (c *Config) NameFromHostname() {
// Reset removes all server configuration files.
func (c *Config) Reset() error {
if err := os.RemoveAll(filepath.Join(c.DataDir, "info")); err != nil {
return err
}
if err := os.RemoveAll(filepath.Join(c.DataDir, "log")); err != nil {
return err
}
@ -338,66 +394,18 @@ func (c *Config) Reset() error {
return nil
}
// Reads the info file from the file system or initializes it based on the config.
func (c *Config) Info() (*Info, error) {
info := &Info{}
path := filepath.Join(c.DataDir, "info")
// Open info file and read it out.
f, err := os.Open(path)
if err != nil && !os.IsNotExist(err) {
return nil, err
} else if f != nil {
defer f.Close()
if err := json.NewDecoder(f).Decode(&info); err != nil {
return nil, err
}
return info, nil
}
// If the file doesn't exist then initialize it.
info.Name = strings.TrimSpace(c.Name)
info.EtcdURL = c.Addr
info.EtcdListenHost = c.BindAddr
info.RaftURL = c.Peer.Addr
info.RaftListenHost = c.Peer.BindAddr
info.EtcdTLS = c.TLSInfo()
info.RaftTLS = c.PeerTLSInfo()
// Write to file.
f, err = os.Create(path)
if err != nil {
return nil, err
}
defer f.Close()
if err := json.NewEncoder(f).Encode(info); err != nil {
return nil, err
}
return info, nil
}
// Sanitize cleans the input fields.
func (c *Config) Sanitize() error {
tlsConfig, err := c.TLSConfig()
if err != nil {
return err
}
peerTlsConfig, err := c.PeerTLSConfig()
if err != nil {
return err
}
var err error
// Sanitize the URLs first.
if c.Addr, err = sanitizeURL(c.Addr, tlsConfig.Scheme); err != nil {
if c.Addr, err = sanitizeURL(c.Addr, c.EtcdTLSInfo().Scheme()); err != nil {
return fmt.Errorf("Advertised URL: %s", err)
}
if c.BindAddr, err = sanitizeBindAddr(c.BindAddr, c.Addr); err != nil {
return fmt.Errorf("Listen Host: %s", err)
}
if c.Peer.Addr, err = sanitizeURL(c.Peer.Addr, peerTlsConfig.Scheme); err != nil {
if c.Peer.Addr, err = sanitizeURL(c.Peer.Addr, c.PeerTLSInfo().Scheme()); err != nil {
return fmt.Errorf("Peer Advertised URL: %s", err)
}
if c.Peer.BindAddr, err = sanitizeBindAddr(c.Peer.BindAddr, c.Peer.Addr); err != nil {
@ -410,39 +418,40 @@ func (c *Config) Sanitize() error {
c.NameFromHostname()
}
if c.DataDir == "" && c.Name != "" {
if c.DataDir == "" && c.Name != "" && !c.ShowVersion && !c.ShowHelp {
c.DataDirFromName()
}
return nil
}
// TLSInfo retrieves a TLSInfo object for the client server.
func (c *Config) TLSInfo() TLSInfo {
return TLSInfo{
CAFile: c.CAFile,
CertFile: c.CertFile,
KeyFile: c.KeyFile,
// EtcdTLSInfo retrieves a TLSInfo object for the etcd server
func (c *Config) EtcdTLSInfo() server.TLSInfo {
return server.TLSInfo{
CAFile: c.CAFile,
CertFile: c.CertFile,
KeyFile: c.KeyFile,
}
}
// ClientTLSConfig generates the TLS configuration for the client server.
func (c *Config) TLSConfig() (TLSConfig, error) {
return c.TLSInfo().Config()
}
// PeerTLSInfo retrieves a TLSInfo object for the peer server.
func (c *Config) PeerTLSInfo() TLSInfo {
return TLSInfo{
CAFile: c.Peer.CAFile,
CertFile: c.Peer.CertFile,
KeyFile: c.Peer.KeyFile,
// PeerRaftInfo retrieves a TLSInfo object for the peer server.
func (c *Config) PeerTLSInfo() server.TLSInfo {
return server.TLSInfo{
CAFile: c.Peer.CAFile,
CertFile: c.Peer.CertFile,
KeyFile: c.Peer.KeyFile,
}
}
// PeerTLSConfig generates the TLS configuration for the peer server.
func (c *Config) PeerTLSConfig() (TLSConfig, error) {
return c.PeerTLSInfo().Config()
// MetricsBucketName generates the name that should be used for a
// corresponding MetricsBucket object
func (c *Config) MetricsBucketName() string {
return fmt.Sprintf("etcd.%s", c.Name)
}
// Trace determines if any trace-level information should be emitted
func (c *Config) Trace() bool {
return c.strTrace == "*"
}
// sanitizeURL will cleanup a host string in the format hostname[:port] and

View File

@ -1,12 +1,12 @@
package server
package config
import (
"io/ioutil"
"os"
"testing"
"github.com/BurntSushi/toml"
"github.com/stretchr/testify/assert"
"github.com/coreos/etcd/third_party/github.com/BurntSushi/toml"
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)
// Ensures that a configuration can be deserialized from TOML.
@ -18,6 +18,7 @@ func TestConfigTOML(t *testing.T) {
cors = ["*"]
cpu_profile_file = "XXX"
data_dir = "/tmp/data"
discovery = "http://example.com/foobar"
key_file = "/tmp/file.key"
bind_addr = "127.0.0.1:4003"
peers = ["coreos.com:4001", "coreos.com:4002"]
@ -29,7 +30,6 @@ func TestConfigTOML(t *testing.T) {
snapshot = true
verbose = true
very_verbose = true
web_url = "/web"
[peer]
addr = "127.0.0.1:7002"
@ -38,7 +38,7 @@ func TestConfigTOML(t *testing.T) {
key_file = "/tmp/peer/file.key"
bind_addr = "127.0.0.1:7003"
`
c := NewConfig()
c := New()
_, err := toml.Decode(content, &c)
assert.Nil(t, err, "")
assert.Equal(t, c.Addr, "127.0.0.1:4002", "")
@ -46,6 +46,7 @@ func TestConfigTOML(t *testing.T) {
assert.Equal(t, c.CertFile, "/tmp/file.cert", "")
assert.Equal(t, c.CorsOrigins, []string{"*"}, "")
assert.Equal(t, c.DataDir, "/tmp/data", "")
assert.Equal(t, c.Discovery, "http://example.com/foobar", "")
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
@ -71,6 +72,7 @@ func TestConfigEnv(t *testing.T) {
os.Setenv("ETCD_CPU_PROFILE_FILE", "XXX")
os.Setenv("ETCD_CORS", "localhost:4001,localhost:4002")
os.Setenv("ETCD_DATA_DIR", "/tmp/data")
os.Setenv("ETCD_DISCOVERY", "http://example.com/foobar")
os.Setenv("ETCD_KEY_FILE", "/tmp/file.key")
os.Setenv("ETCD_BIND_ADDR", "127.0.0.1:4003")
os.Setenv("ETCD_PEERS", "coreos.com:4001,coreos.com:4002")
@ -82,19 +84,19 @@ func TestConfigEnv(t *testing.T) {
os.Setenv("ETCD_SNAPSHOT", "true")
os.Setenv("ETCD_VERBOSE", "1")
os.Setenv("ETCD_VERY_VERBOSE", "yes")
os.Setenv("ETCD_WEB_URL", "/web")
os.Setenv("ETCD_PEER_ADDR", "127.0.0.1:7002")
os.Setenv("ETCD_PEER_CA_FILE", "/tmp/peer/file.ca")
os.Setenv("ETCD_PEER_CERT_FILE", "/tmp/peer/file.cert")
os.Setenv("ETCD_PEER_KEY_FILE", "/tmp/peer/file.key")
os.Setenv("ETCD_PEER_BIND_ADDR", "127.0.0.1:7003")
c := NewConfig()
c := New()
c.LoadEnv()
assert.Equal(t, c.CAFile, "/tmp/file.ca", "")
assert.Equal(t, c.CertFile, "/tmp/file.cert", "")
assert.Equal(t, c.CorsOrigins, []string{"localhost:4001", "localhost:4002"}, "")
assert.Equal(t, c.DataDir, "/tmp/data", "")
assert.Equal(t, c.Discovery, "http://example.com/foobar", "")
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
@ -111,39 +113,42 @@ func TestConfigEnv(t *testing.T) {
assert.Equal(t, c.Peer.CertFile, "/tmp/peer/file.cert", "")
assert.Equal(t, c.Peer.KeyFile, "/tmp/peer/file.key", "")
assert.Equal(t, c.Peer.BindAddr, "127.0.0.1:7003", "")
// Clear this as it will mess up other tests
os.Setenv("ETCD_DISCOVERY", "")
}
// Ensures that the "help" flag can be parsed.
func TestConfigHelpFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-help"}), "")
assert.True(t, c.ShowHelp)
}
// Ensures that the abbreviated "help" flag can be parsed.
func TestConfigAbbreviatedHelpFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-h"}), "")
assert.True(t, c.ShowHelp)
}
// Ensures that the "version" flag can be parsed.
func TestConfigVersionFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-version"}), "")
assert.True(t, c.ShowVersion)
}
// Ensures that the "force config" flag can be parsed.
func TestConfigForceFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-force"}), "")
assert.True(t, c.Force)
}
// Ensures that the abbreviated "force config" flag can be parsed.
func TestConfigAbbreviatedForceFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-f"}), "")
assert.True(t, c.Force)
}
@ -158,7 +163,7 @@ func TestConfigAddrEnv(t *testing.T) {
// Ensures that a the advertised flag can be parsed.
func TestConfigAddrFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4002"}), "")
assert.Equal(t, c.Addr, "127.0.0.1:4002", "")
}
@ -173,7 +178,7 @@ func TestConfigCAFileEnv(t *testing.T) {
// Ensures that a the CA file flag can be parsed.
func TestConfigCAFileFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-ca-file", "/tmp/file.ca"}), "")
assert.Equal(t, c.CAFile, "/tmp/file.ca", "")
}
@ -188,7 +193,7 @@ func TestConfigCertFileEnv(t *testing.T) {
// Ensures that a the Cert file flag can be parsed.
func TestConfigCertFileFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-cert-file", "/tmp/file.cert"}), "")
assert.Equal(t, c.CertFile, "/tmp/file.cert", "")
}
@ -203,7 +208,7 @@ func TestConfigKeyFileEnv(t *testing.T) {
// Ensures that a the Key file flag can be parsed.
func TestConfigKeyFileFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-key-file", "/tmp/file.key"}), "")
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
}
@ -218,14 +223,14 @@ func TestConfigBindAddrEnv(t *testing.T) {
// Ensures that a the Listen Host file flag can be parsed.
func TestConfigBindAddrFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-bind-addr", "127.0.0.1:4003"}), "")
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
}
// Ensures that a the Listen Host port overrides the advertised port
func TestConfigBindAddrOverride(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4009", "-bind-addr", "127.0.0.1:4010"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "127.0.0.1:4010", "")
@ -233,7 +238,7 @@ func TestConfigBindAddrOverride(t *testing.T) {
// Ensures that a the Listen Host inherits its port from the advertised addr
func TestConfigBindAddrInheritPort(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4009", "-bind-addr", "127.0.0.1"}), "")
assert.Nil(t, c.Sanitize())
assert.Equal(t, c.BindAddr, "127.0.0.1:4009", "")
@ -241,7 +246,7 @@ func TestConfigBindAddrInheritPort(t *testing.T) {
// Ensures that a port only argument errors out
func TestConfigBindAddrErrorOnNoHost(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-addr", "127.0.0.1:4009", "-bind-addr", ":4010"}), "")
assert.Error(t, c.Sanitize())
}
@ -256,7 +261,7 @@ func TestConfigPeersEnv(t *testing.T) {
// Ensures that a the Peers flag can be parsed.
func TestConfigPeersFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peers", "coreos.com:4001,coreos.com:4002"}), "")
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
}
@ -271,7 +276,7 @@ func TestConfigPeersFileEnv(t *testing.T) {
// Ensures that a the Peers File flag can be parsed.
func TestConfigPeersFileFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peers-file", "/tmp/peers"}), "")
assert.Equal(t, c.PeersFile, "/tmp/peers", "")
}
@ -286,7 +291,7 @@ func TestConfigMaxClusterSizeEnv(t *testing.T) {
// Ensures that a the Max Cluster Size flag can be parsed.
func TestConfigMaxClusterSizeFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-max-cluster-size", "5"}), "")
assert.Equal(t, c.MaxClusterSize, 5, "")
}
@ -301,7 +306,7 @@ func TestConfigMaxResultBufferEnv(t *testing.T) {
// Ensures that a the Max Result Buffer flag can be parsed.
func TestConfigMaxResultBufferFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-max-result-buffer", "512"}), "")
assert.Equal(t, c.MaxResultBuffer, 512, "")
}
@ -316,7 +321,7 @@ func TestConfigMaxRetryAttemptsEnv(t *testing.T) {
// Ensures that a the Max Retry Attempts flag can be parsed.
func TestConfigMaxRetryAttemptsFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-max-retry-attempts", "10"}), "")
assert.Equal(t, c.MaxRetryAttempts, 10, "")
}
@ -331,14 +336,14 @@ func TestConfigNameEnv(t *testing.T) {
// Ensures that a the Name flag can be parsed.
func TestConfigNameFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-name", "test-name"}), "")
assert.Equal(t, c.Name, "test-name", "")
}
// Ensures that a Name gets guessed if not specified
func TestConfigNameGuess(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{}), "")
assert.Nil(t, c.Sanitize())
name, _ := os.Hostname()
@ -347,7 +352,7 @@ func TestConfigNameGuess(t *testing.T) {
// Ensures that a DataDir gets guessed if not specified
func TestConfigDataDirGuess(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{}), "")
assert.Nil(t, c.Sanitize())
name, _ := os.Hostname()
@ -364,7 +369,7 @@ func TestConfigSnapshotEnv(t *testing.T) {
// Ensures that a the Snapshot flag can be parsed.
func TestConfigSnapshotFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-snapshot"}), "")
assert.Equal(t, c.Snapshot, true, "")
}
@ -379,7 +384,7 @@ func TestConfigVerboseEnv(t *testing.T) {
// Ensures that a the Verbose flag can be parsed.
func TestConfigVerboseFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-v"}), "")
assert.Equal(t, c.Verbose, true, "")
}
@ -394,7 +399,7 @@ func TestConfigVeryVerboseEnv(t *testing.T) {
// Ensures that a the Very Verbose flag can be parsed.
func TestConfigVeryVerboseFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-vv"}), "")
assert.Equal(t, c.VeryVerbose, true, "")
}
@ -409,7 +414,7 @@ func TestConfigPeerAddrEnv(t *testing.T) {
// Ensures that a the Peer Advertised URL flag can be parsed.
func TestConfigPeerAddrFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-addr", "localhost:7002"}), "")
assert.Equal(t, c.Peer.Addr, "localhost:7002", "")
}
@ -424,7 +429,7 @@ func TestConfigPeerCAFileEnv(t *testing.T) {
// Ensures that a the Peer CA file flag can be parsed.
func TestConfigPeerCAFileFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-ca-file", "/tmp/peer/file.ca"}), "")
assert.Equal(t, c.Peer.CAFile, "/tmp/peer/file.ca", "")
}
@ -439,7 +444,7 @@ func TestConfigPeerCertFileEnv(t *testing.T) {
// Ensures that a the Cert file flag can be parsed.
func TestConfigPeerCertFileFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-cert-file", "/tmp/peer/file.cert"}), "")
assert.Equal(t, c.Peer.CertFile, "/tmp/peer/file.cert", "")
}
@ -454,7 +459,7 @@ func TestConfigPeerKeyFileEnv(t *testing.T) {
// Ensures that a the Peer Key file flag can be parsed.
func TestConfigPeerKeyFileFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-key-file", "/tmp/peer/file.key"}), "")
assert.Equal(t, c.Peer.KeyFile, "/tmp/peer/file.key", "")
}
@ -469,7 +474,7 @@ func TestConfigPeerBindAddrEnv(t *testing.T) {
// Ensures that a bad flag returns an error.
func TestConfigBadFlag(t *testing.T) {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-no-such-flag"})
assert.Error(t, err)
assert.Equal(t, err.Error(), `flag provided but not defined: -no-such-flag`)
@ -477,7 +482,7 @@ func TestConfigBadFlag(t *testing.T) {
// Ensures that a the Peer Listen Host file flag can be parsed.
func TestConfigPeerBindAddrFlag(t *testing.T) {
c := NewConfig()
c := New()
assert.Nil(t, c.LoadFlags([]string{"-peer-bind-addr", "127.0.0.1:4003"}), "")
assert.Equal(t, c.Peer.BindAddr, "127.0.0.1:4003", "")
}
@ -488,7 +493,7 @@ func TestConfigCustomConfigOverrideSystemConfig(t *testing.T) {
custom := `addr = "127.0.0.1:6000"`
withTempFile(system, func(p1 string) {
withTempFile(custom, func(p2 string) {
c := NewConfig()
c := New()
c.SystemPath = p1
assert.Nil(t, c.Load([]string{"-config", p2}), "")
assert.Equal(t, c.Addr, "http://127.0.0.1:6000", "")
@ -503,7 +508,7 @@ func TestConfigEnvVarOverrideCustomConfig(t *testing.T) {
custom := `[peer]` + "\n" + `advertised_url = "127.0.0.1:9000"`
withTempFile(custom, func(path string) {
c := NewConfig()
c := New()
c.SystemPath = ""
assert.Nil(t, c.Load([]string{"-config", path}), "")
assert.Equal(t, c.Peer.Addr, "http://127.0.0.1:8000", "")
@ -515,7 +520,7 @@ func TestConfigCLIArgsOverrideEnvVar(t *testing.T) {
os.Setenv("ETCD_ADDR", "127.0.0.1:1000")
defer os.Setenv("ETCD_ADDR", "")
c := NewConfig()
c := New()
c.SystemPath = ""
assert.Nil(t, c.Load([]string{"-addr", "127.0.0.1:2000"}), "")
assert.Equal(t, c.Addr, "http://127.0.0.1:2000", "")
@ -527,162 +532,162 @@ func TestConfigCLIArgsOverrideEnvVar(t *testing.T) {
func TestConfigDeprecatedAddrFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-c", "127.0.0.1:4002"})
assert.NoError(t, err)
assert.Equal(t, c.Addr, "127.0.0.1:4002")
})
assert.Equal(t, stderr, "[deprecated] use -addr, not -c")
assert.Equal(t, stderr, "[deprecated] use -addr, not -c\n")
}
func TestConfigDeprecatedBindAddrFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-cl", "127.0.0.1:4003"})
assert.NoError(t, err)
assert.Equal(t, c.BindAddr, "127.0.0.1:4003", "")
})
assert.Equal(t, stderr, "[deprecated] use -bind-addr, not -cl", "")
assert.Equal(t, stderr, "[deprecated] use -bind-addr, not -cl\n", "")
}
func TestConfigDeprecatedCAFileFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-clientCAFile", "/tmp/file.ca"})
assert.NoError(t, err)
assert.Equal(t, c.CAFile, "/tmp/file.ca", "")
})
assert.Equal(t, stderr, "[deprecated] use -ca-file, not -clientCAFile", "")
assert.Equal(t, stderr, "[deprecated] use -ca-file, not -clientCAFile\n", "")
}
func TestConfigDeprecatedCertFileFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-clientCert", "/tmp/file.cert"})
assert.NoError(t, err)
assert.Equal(t, c.CertFile, "/tmp/file.cert", "")
})
assert.Equal(t, stderr, "[deprecated] use -cert-file, not -clientCert", "")
assert.Equal(t, stderr, "[deprecated] use -cert-file, not -clientCert\n", "")
}
func TestConfigDeprecatedKeyFileFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-clientKey", "/tmp/file.key"})
assert.NoError(t, err)
assert.Equal(t, c.KeyFile, "/tmp/file.key", "")
})
assert.Equal(t, stderr, "[deprecated] use -key-file, not -clientKey", "")
assert.Equal(t, stderr, "[deprecated] use -key-file, not -clientKey\n", "")
}
func TestConfigDeprecatedPeersFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-C", "coreos.com:4001,coreos.com:4002"})
assert.NoError(t, err)
assert.Equal(t, c.Peers, []string{"coreos.com:4001", "coreos.com:4002"}, "")
})
assert.Equal(t, stderr, "[deprecated] use -peers, not -C", "")
assert.Equal(t, stderr, "[deprecated] use -peers, not -C\n", "")
}
func TestConfigDeprecatedPeersFileFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-CF", "/tmp/machines"})
assert.NoError(t, err)
assert.Equal(t, c.PeersFile, "/tmp/machines", "")
})
assert.Equal(t, stderr, "[deprecated] use -peers-file, not -CF", "")
assert.Equal(t, stderr, "[deprecated] use -peers-file, not -CF\n", "")
}
func TestConfigDeprecatedMaxClusterSizeFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-maxsize", "5"})
assert.NoError(t, err)
assert.Equal(t, c.MaxClusterSize, 5, "")
})
assert.Equal(t, stderr, "[deprecated] use -max-cluster-size, not -maxsize", "")
assert.Equal(t, stderr, "[deprecated] use -max-cluster-size, not -maxsize\n", "")
}
func TestConfigDeprecatedMaxResultBufferFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-m", "512"})
assert.NoError(t, err)
assert.Equal(t, c.MaxResultBuffer, 512, "")
})
assert.Equal(t, stderr, "[deprecated] use -max-result-buffer, not -m", "")
assert.Equal(t, stderr, "[deprecated] use -max-result-buffer, not -m\n", "")
}
func TestConfigDeprecatedMaxRetryAttemptsFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-r", "10"})
assert.NoError(t, err)
assert.Equal(t, c.MaxRetryAttempts, 10, "")
})
assert.Equal(t, stderr, "[deprecated] use -max-retry-attempts, not -r", "")
assert.Equal(t, stderr, "[deprecated] use -max-retry-attempts, not -r\n", "")
}
func TestConfigDeprecatedNameFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-n", "test-name"})
assert.NoError(t, err)
assert.Equal(t, c.Name, "test-name", "")
})
assert.Equal(t, stderr, "[deprecated] use -name, not -n", "")
assert.Equal(t, stderr, "[deprecated] use -name, not -n\n", "")
}
func TestConfigDeprecatedPeerAddrFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-s", "localhost:7002"})
assert.NoError(t, err)
assert.Equal(t, c.Peer.Addr, "localhost:7002", "")
})
assert.Equal(t, stderr, "[deprecated] use -peer-addr, not -s", "")
assert.Equal(t, stderr, "[deprecated] use -peer-addr, not -s\n", "")
}
func TestConfigDeprecatedPeerBindAddrFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-sl", "127.0.0.1:4003"})
assert.NoError(t, err)
assert.Equal(t, c.Peer.BindAddr, "127.0.0.1:4003", "")
})
assert.Equal(t, stderr, "[deprecated] use -peer-bind-addr, not -sl", "")
assert.Equal(t, stderr, "[deprecated] use -peer-bind-addr, not -sl\n", "")
}
func TestConfigDeprecatedPeerCAFileFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-serverCAFile", "/tmp/peer/file.ca"})
assert.NoError(t, err)
assert.Equal(t, c.Peer.CAFile, "/tmp/peer/file.ca", "")
})
assert.Equal(t, stderr, "[deprecated] use -peer-ca-file, not -serverCAFile", "")
assert.Equal(t, stderr, "[deprecated] use -peer-ca-file, not -serverCAFile\n", "")
}
func TestConfigDeprecatedPeerCertFileFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-serverCert", "/tmp/peer/file.cert"})
assert.NoError(t, err)
assert.Equal(t, c.Peer.CertFile, "/tmp/peer/file.cert", "")
})
assert.Equal(t, stderr, "[deprecated] use -peer-cert-file, not -serverCert", "")
assert.Equal(t, stderr, "[deprecated] use -peer-cert-file, not -serverCert\n", "")
}
func TestConfigDeprecatedPeerKeyFileFlag(t *testing.T) {
_, stderr := capture(func() {
c := NewConfig()
c := New()
err := c.LoadFlags([]string{"-serverKey", "/tmp/peer/file.key"})
assert.NoError(t, err)
assert.Equal(t, c.Peer.KeyFile, "/tmp/peer/file.key", "")
})
assert.Equal(t, stderr, "[deprecated] use -peer-key-file, not -serverKey", "")
assert.Equal(t, stderr, "[deprecated] use -peer-key-file, not -serverKey\n", "")
}
//--------------------------------------
@ -693,7 +698,7 @@ func TestConfigDeprecatedPeerKeyFileFlag(t *testing.T) {
func withEnv(key, value string, f func(c *Config)) {
os.Setenv(key, value)
defer os.Setenv(key, "")
c := NewConfig()
c := New()
f(c)
}

9
config/timeout.go Normal file
View File

@ -0,0 +1,9 @@
package config
const (
// The amount of time (in ms) to elapse without a heartbeat before becoming a candidate
defaultElectionTimeout = 200
// The frequency (in ms) by which heartbeats are sent to followers.
defaultHeartbeatTimeout = 50
)

View File

@ -0,0 +1,9 @@
FROM stackbrew/ubuntu:raring
RUN apt-get update && apt-get install -y collectd
RUN adduser --system --group --no-create-home collectd
ADD collectd.conf /etc/collectd/collectd.conf.tmpl
ADD collectd-wrapper /bin/collectd-wrapper
RUN chown -R collectd:collectd /etc/collectd
CMD ["collectd-wrapper"]

20
contrib/collectd/README Normal file
View File

@ -0,0 +1,20 @@
We're going to use Docker to build a chroot env that can be run with systemd-nspawn since I cannot figure out how to run
a container using docker in the global network namespace.
1. Build the collectd image using docker
docker build -t collectd .
2. Run the container (since we have to run it to export it...)
COLLECTD_CONTAINER=`docker run -name collectd-tmp -d collectd`
3. Export then kill the container
docker export collectd-tmp > /tmp/collectd.tar
4. Kill the temporary container
docker kill $COLLECTD_CONTAINER
5. Unpack the tar archive
mkdir -p /tmp/collectd && tar -xvf /tmp/collectd.tar -C /tmp/collectd/
6. Run collectd with systemd-nspawn - replace the COLLECTD_* env vars with your parameters!
sudo systemd-run --unit collectd systemd-nspawn -D /tmp/collectd /bin/bash -c "COLLECTD_GRAPHITE_HOSTNAME=172.31.13.241 COLLECTD_LOCAL_HOSTNAME=node1 /bin/collectd-wrapper"

View File

@ -0,0 +1,16 @@
#!/bin/bash
cat /etc/collectd/collectd.conf.tmpl > /etc/collectd/collectd.conf
cat << EOF >> /etc/collectd/collectd.conf
Hostname "${COLLECTD_LOCAL_HOSTNAME}"
<Plugin write_graphite>
<Carbon>
Host "${COLLECTD_GRAPHITE_HOSTNAME}"
Port "2003"
</Carbon>
</Plugin>
EOF
collectd -C /etc/collectd/collectd.conf -f

View File

@ -0,0 +1,898 @@
# Config file for collectd(1).
#
# Some plugins need additional configuration and are disabled by default.
# Please read collectd.conf(5) for details.
#
# You should also read /usr/share/doc/collectd-core/README.Debian.plugins
# before enabling any more plugins.
#Hostname "localhost"
#FQDNLookup true
#BaseDir "/var/lib/collectd"
#PluginDir "/usr/lib/collectd"
#TypesDB "/usr/share/collectd/types.db" "/etc/collectd/my_types.db"
#Interval 10
#Timeout 2
#ReadThreads 5
LoadPlugin logfile
#LoadPlugin syslog
<Plugin logfile>
LogLevel "info"
File STDOUT
Timestamp true
PrintSeverity false
</Plugin>
#<Plugin syslog>
# LogLevel info
#</Plugin>
#LoadPlugin amqp
#LoadPlugin apache
#LoadPlugin apcups
#LoadPlugin ascent
#LoadPlugin battery
#LoadPlugin bind
#LoadPlugin conntrack
#LoadPlugin contextswitch
LoadPlugin cpu
#LoadPlugin cpufreq
#LoadPlugin csv
#LoadPlugin curl
#LoadPlugin curl_json
#LoadPlugin curl_xml
#LoadPlugin dbi
LoadPlugin df
#LoadPlugin disk
#LoadPlugin dns
#LoadPlugin email
#LoadPlugin entropy
#LoadPlugin ethstat
#LoadPlugin exec
#LoadPlugin filecount
#LoadPlugin fscache
#LoadPlugin gmond
#LoadPlugin hddtemp
#LoadPlugin interface
#LoadPlugin ipmi
#LoadPlugin iptables
#LoadPlugin ipvs
#LoadPlugin irq
#LoadPlugin java
#LoadPlugin libvirt
#LoadPlugin load
#LoadPlugin madwifi
#LoadPlugin mbmon
#LoadPlugin md
#LoadPlugin memcachec
#LoadPlugin memcached
LoadPlugin memory
#LoadPlugin multimeter
#LoadPlugin mysql
#LoadPlugin netlink
#LoadPlugin network
#LoadPlugin nfs
#LoadPlugin nginx
#LoadPlugin notify_desktop
#LoadPlugin notify_email
#LoadPlugin ntpd
#LoadPlugin numa
#LoadPlugin nut
#LoadPlugin olsrd
#LoadPlugin openvpn
#<LoadPlugin perl>
# Globals true
#</LoadPlugin>
#LoadPlugin pinba
#LoadPlugin ping
#LoadPlugin postgresql
#LoadPlugin powerdns
#LoadPlugin processes
#LoadPlugin protocols
#<LoadPlugin python>
# Globals true
#</LoadPlugin>
#LoadPlugin rrdcached
#LoadPlugin rrdtool
#LoadPlugin sensors
#LoadPlugin serial
#LoadPlugin snmp
#LoadPlugin swap
#LoadPlugin table
#LoadPlugin tail
LoadPlugin tcpconns
#LoadPlugin teamspeak2
#LoadPlugin ted
#LoadPlugin thermal
#LoadPlugin tokyotyrant
#LoadPlugin unixsock
#LoadPlugin uptime
#LoadPlugin users
#LoadPlugin uuid
#LoadPlugin varnish
#LoadPlugin vmem
#LoadPlugin vserver
#LoadPlugin wireless
LoadPlugin write_graphite
#LoadPlugin write_http
#LoadPlugin write_mongodb
#<Plugin amqp>
# <Publish "name">
# Host "localhost"
# Port "5672"
# VHost "/"
# User "guest"
# Password "guest"
# Exchange "amq.fanout"
# RoutingKey "collectd"
# Persistent false
# StoreRates false
# </Publish>
#</Plugin>
#<Plugin apache>
# <Instance "foo">
# URL "http://localhost/server-status?auto"
# User "www-user"
# Password "secret"
# VerifyPeer false
# VerifyHost false
# CACert "/etc/ssl/ca.crt"
# Server "apache"
# </Instance>
#
# <Instance "bar">
# URL "http://some.domain.tld/status?auto"
# Host "some.domain.tld"
# Server "lighttpd"
# </Instance>
#</Plugin>
#<Plugin apcups>
# Host "localhost"
# Port "3551"
#</Plugin>
#<Plugin ascent>
# URL "http://localhost/ascent/status/"
# User "www-user"
# Password "secret"
# VerifyPeer false
# VerifyHost false
# CACert "/etc/ssl/ca.crt"
#</Plugin>
#<Plugin "bind">
# URL "http://localhost:8053/"
#
# ParseTime false
#
# OpCodes true
# QTypes true
# ServerStats true
# ZoneMaintStats true
# ResolverStats false
# MemoryStats true
#
# <View "_default">
# QTypes true
# ResolverStats true
# CacheRRSets true
#
# Zone "127.in-addr.arpa/IN"
# </View>
#</Plugin>
#<Plugin csv>
# DataDir "/var/lib/collectd/csv"
# StoreRates false
#</Plugin>
#<Plugin curl>
# <Page "stock_quotes">
# URL "http://finance.google.com/finance?q=NYSE%3AAMD"
# User "foo"
# Password "bar"
# VerifyPeer false
# VerifyHost false
# CACert "/etc/ssl/ca.crt"
# MeasureResponseTime false
# <Match>
# Regex "<span +class=\"pr\"[^>]*> *([0-9]*\\.[0-9]+) *</span>"
# DSType "GaugeAverage"
# Type "stock_value"
# Instance "AMD"
# </Match>
# </Page>
#</Plugin>
#<Plugin curl_json>
## See: http://wiki.apache.org/couchdb/Runtime_Statistics
# <URL "http://localhost:5984/_stats">
# Instance "httpd"
# <Key "httpd/requests/count">
# Type "http_requests"
# </Key>
#
# <Key "httpd_request_methods/*/count">
# Type "http_request_methods"
# </Key>
#
# <Key "httpd_status_codes/*/count">
# Type "http_response_codes"
# </Key>
# </URL>
## Database status metrics:
# <URL "http://localhost:5984/_all_dbs">
# Instance "dbs"
# <Key "*/doc_count">
# Type "gauge"
# </Key>
# <Key "*/doc_del_count">
# Type "counter"
# </Key>
# <Key "*/disk_size">
# Type "bytes"
# </Key>
# </URL>
#</Plugin>
#<Plugin "curl_xml">
# <URL "http://localhost/stats.xml">
# Host "my_host"
# Instance "some_instance"
# User "collectd"
# Password "thaiNg0I"
# VerifyPeer true
# VerifyHost true
# CACert "/path/to/ca.crt"
#
# <XPath "table[@id=\"magic_level\"]/tr">
# Type "magic_level"
# InstancePrefix "prefix-"
# InstanceFrom "td[1]"
# ValuesFrom "td[2]/span[@class=\"level\"]"
# </XPath>
# </URL>
#</Plugin>
#<Plugin dbi>
# <Query "num_of_customers">
# Statement "SELECT 'customers' AS c_key, COUNT(*) AS c_value \
# FROM customers_tbl"
# MinVersion 40102
# MaxVersion 50042
# <Result>
# Type "gauge"
# InstancePrefix "customer"
# InstancesFrom "c_key"
# ValuesFrom "c_value"
# </Result>
# </Query>
#
# <Database "customers_db">
# Driver "mysql"
# DriverOption "host" "localhost"
# DriverOption "username" "collectd"
# DriverOption "password" "secret"
# DriverOption "dbname" "custdb0"
# SelectDB "custdb0"
# Query "num_of_customers"
# Query "..."
# </Database>
#</Plugin>
#<Plugin df>
# Device "/dev/sda1"
# Device "192.168.0.2:/mnt/nfs"
# MountPoint "/home"
# FSType "ext3"
# IgnoreSelected false
# ReportByDevice false
# ReportReserved false
# ReportInodes false
#</Plugin>
#<Plugin disk>
# Disk "hda"
# Disk "/sda[23]/"
# IgnoreSelected false
#</Plugin>
#<Plugin dns>
# Interface "eth0"
# IgnoreSource "192.168.0.1"
# SelectNumericQueryTypes false
#</Plugin>
#<Plugin email>
# SocketFile "/var/run/collectd-email"
# SocketGroup "collectd"
# SocketPerms "0770"
# MaxConns 5
#</Plugin>
#<Plugin ethstat>
# Interface "eth0"
# Map "rx_csum_offload_errors" "if_rx_errors" "checksum_offload"
# Map "multicast" "if_multicast"
# MappedOnly false
#</Plugin>
#<Plugin exec>
# Exec user "/path/to/exec"
# Exec "user:group" "/path/to/exec"
# NotificationExec user "/path/to/exec"
#</Plugin>
#<Plugin filecount>
# <Directory "/path/to/dir">
# Instance "foodir"
# Name "*.conf"
# MTime "-5m"
# Size "+10k"
# Recursive true
# IncludeHidden false
# </Directory>
#</Plugin>
#<Plugin gmond>
# MCReceiveFrom "239.2.11.71" "8649"
#
# <Metric "swap_total">
# Type "swap"
# TypeInstance "total"
# DataSource "value"
# </Metric>
#
# <Metric "swap_free">
# Type "swap"
# TypeInstance "free"
# DataSource "value"
# </Metric>
#</Plugin>
#<Plugin hddtemp>
# Host "127.0.0.1"
# Port 7634
#</Plugin>
#<Plugin interface>
# Interface "eth0"
# IgnoreSelected false
#</Plugin>
#<Plugin ipmi>
# Sensor "some_sensor"
# Sensor "another_one"
# IgnoreSelected false
# NotifySensorAdd false
# NotifySensorRemove true
# NotifySensorNotPresent false
#</Plugin>
#<Plugin iptables>
# Chain "table" "chain"
#</Plugin>
#<Plugin irq>
# Irq 7
# Irq 8
# Irq 9
# IgnoreSelected true
#</Plugin>
#<Plugin java>
# JVMArg "-verbose:jni"
# JVMArg "-Djava.class.path=/usr/share/collectd/java/collectd-api.jar"
#
# LoadPlugin "org.collectd.java.GenericJMX"
# <Plugin "GenericJMX">
# # See /usr/share/doc/collectd/examples/GenericJMX.conf
# # for an example config.
# </Plugin>
#</Plugin>
#<Plugin libvirt>
# Connection "xen:///"
# RefreshInterval 60
# Domain "name"
# BlockDevice "name:device"
# InterfaceDevice "name:device"
# IgnoreSelected false
# HostnameFormat name
# InterfaceFormat name
#</Plugin>
#<Plugin madwifi>
# Interface "wlan0"
# IgnoreSelected false
# Source "SysFS"
# WatchSet "None"
# WatchAdd "node_octets"
# WatchAdd "node_rssi"
# WatchAdd "is_rx_acl"
# WatchAdd "is_scan_active"
#</Plugin>
#<Plugin mbmon>
# Host "127.0.0.1"
# Port 411
#</Plugin>
#<Plugin md>
# Device "/dev/md0"
# IgnoreSelected false
#</Plugin>
#<Plugin memcachec>
# <Page "plugin_instance">
# Server "localhost"
# Key "page_key"
# <Match>
# Regex "(\\d+) bytes sent"
# ExcludeRegex "<lines to be excluded>"
# DSType CounterAdd
# Type "ipt_octets"
# Instance "type_instance"
# </Match>
# </Page>
#</Plugin>
#<Plugin memcached>
# Socket "/var/run/memcached.sock"
# or:
# Host "127.0.0.1"
# Port "11211"
#</Plugin>
#<Plugin mysql>
# <Database db_name>
# Host "database.serv.er"
# Port "3306"
# User "db_user"
# Password "secret"
# Database "db_name"
# MasterStats true
# </Database>
#
# <Database db_name2>
# Host "localhost"
# Socket "/var/run/mysql/mysqld.sock"
# SlaveStats true
# SlaveNotifications true
# </Database>
#</Plugin>
#<Plugin netlink>
# Interface "All"
# VerboseInterface "All"
# QDisc "eth0" "pfifo_fast-1:0"
# Class "ppp0" "htb-1:10"
# Filter "ppp0" "u32-1:0"
# IgnoreSelected false
#</Plugin>
#<Plugin network>
# # client setup:
# Server "ff18::efc0:4a42" "25826"
# <Server "239.192.74.66" "25826">
# SecurityLevel Encrypt
# Username "user"
# Password "secret"
# Interface "eth0"
# </Server>
# TimeToLive "128"
#
# # server setup:
# Listen "0.0.0.0" "25826"
# <Listen "239.192.74.66" "25826">
# SecurityLevel Sign
# AuthFile "/etc/collectd/passwd"
# Interface "eth0"
# </Listen>
# MaxPacketSize 1024
#
# # proxy setup (client and server as above):
# Forward true
#
# # statistics about the network plugin itself
# ReportStats false
#
# # "garbage collection"
# CacheFlush 1800
#</Plugin>
#<Plugin nginx>
# URL "http://localhost/status?auto"
# User "www-user"
# Password "secret"
# VerifyPeer false
# VerifyHost false
# CACert "/etc/ssl/ca.crt"
#</Plugin>
#<Plugin notify_desktop>
# OkayTimeout 1000
# WarningTimeout 5000
# FailureTimeout 0
#</Plugin>
#<Plugin notify_email>
# SMTPServer "localhost"
# SMTPPort 25
# SMTPUser "my-username"
# SMTPPassword "my-password"
# From "collectd@main0server.com"
# # <WARNING/FAILURE/OK> on <hostname>.
# # Beware! Do not use not more than two placeholders (%)!
# Subject "[collectd] %s on %s!"
# Recipient "email1@domain1.net"
# Recipient "email2@domain2.com"
#</Plugin>
#<Plugin ntpd>
# Host "localhost"
# Port 123
# ReverseLookups false
#</Plugin>
#<Plugin nut>
# UPS "upsname@hostname:port"
#</Plugin>
#<Plugin olsrd>
# Host "127.0.0.1"
# Port "2006"
# CollectLinks "Summary"
# CollectRoutes "Summary"
# CollectTopology "Summary"
#</Plugin>
#<Plugin openvpn>
# StatusFile "/etc/openvpn/openvpn-status.log"
# ImprovedNamingSchema false
# CollectCompression true
# CollectIndividualUsers true
# CollectUserCount false
#</Plugin>
#<Plugin perl>
# IncludeDir "/my/include/path"
# BaseName "Collectd::Plugins"
# EnableDebugger ""
# LoadPlugin Monitorus
# LoadPlugin OpenVZ
#
# <Plugin foo>
# Foo "Bar"
# Qux "Baz"
# </Plugin>
#</Plugin>
#<Plugin pinba>
# Address "::0"
# Port "30002"
# <View "name">
# Host "host name"
# Server "server name"
# Script "script name"
# <View>
#</Plugin>
#<Plugin ping>
# Host "host.foo.bar"
# Host "host.baz.qux"
# Interval 1.0
# Timeout 0.9
# TTL 255
# SourceAddress "1.2.3.4"
# Device "eth0"
# MaxMissed -1
#</Plugin>
#<Plugin postgresql>
# <Query magic>
# Statement "SELECT magic FROM wizard WHERE host = $1;"
# Param hostname
#
# <Result>
# Type gauge
# InstancePrefix "magic"
# ValuesFrom "magic"
# </Result>
# </Query>
#
# <Query rt36_tickets>
# Statement "SELECT COUNT(type) AS count, type \
# FROM (SELECT CASE \
# WHEN resolved = 'epoch' THEN 'open' \
# ELSE 'resolved' END AS type \
# FROM tickets) type \
# GROUP BY type;"
#
# <Result>
# Type counter
# InstancePrefix "rt36_tickets"
# InstancesFrom "type"
# ValuesFrom "count"
# </Result>
# </Query>
#
# <Database foo>
# Host "hostname"
# Port 5432
# User "username"
# Password "secret"
#
# SSLMode "prefer"
# KRBSrvName "kerberos_service_name"
#
# Query magic
# </Database>
#
# <Database bar>
# Interval 60
# Service "service_name"
#
# Query backend # predefined
# Query rt36_tickets
# </Database>
#</Plugin>
#<Plugin powerdns>
# <Server "server_name">
# Collect "latency"
# Collect "udp-answers" "udp-queries"
# Socket "/var/run/pdns.controlsocket"
# </Server>
# <Recursor "recursor_name">
# Collect "questions"
# Collect "cache-hits" "cache-misses"
# Socket "/var/run/pdns_recursor.controlsocket"
# </Recursor>
# LocalSocket "/opt/collectd/var/run/collectd-powerdns"
#</Plugin>
#<Plugin processes>
# Process "name"
# ProcessMatch "foobar" "/usr/bin/perl foobar\\.pl.*"
#</Plugin>
#<Plugin protocols>
# Value "/^Tcp:/"
# IgnoreSelected false
#</Plugin>
#<Plugin python>
# ModulePath "/path/to/your/python/modules"
# LogTraces true
# Interactive true
# Import "spam"
#
# <Module spam>
# spam "wonderful" "lovely"
# </Module>
#</Plugin>
#<Plugin rrdcached>
# DaemonAddress "unix:/var/run/rrdcached.sock"
# DataDir "/var/lib/rrdcached/db/collectd"
# CreateFiles true
# CollectStatistics true
#</Plugin>
<Plugin rrdtool>
DataDir "/var/lib/collectd/rrd"
# CacheTimeout 120
# CacheFlush 900
# WritesPerSecond 30
# RandomTimeout 0
#
# The following settings are rather advanced
# and should usually not be touched:
# StepSize 10
# HeartBeat 20
# RRARows 1200
# RRATimespan 158112000
# XFF 0.1
</Plugin>
#<Plugin sensors>
# SensorConfigFile "/etc/sensors3.conf"
# Sensor "it8712-isa-0290/temperature-temp1"
# Sensor "it8712-isa-0290/fanspeed-fan3"
# Sensor "it8712-isa-0290/voltage-in8"
# IgnoreSelected false
#</Plugin>
# See /usr/share/doc/collectd/examples/snmp-data.conf.gz for a
# comprehensive sample configuration.
#<Plugin snmp>
# <Data "powerplus_voltge_input">
# Type "voltage"
# Table false
# Instance "input_line1"
# Scale 0.1
# Values "SNMPv2-SMI::enterprises.6050.5.4.1.1.2.1"
# </Data>
# <Data "hr_users">
# Type "users"
# Table false
# Instance ""
# Shift -1
# Values "HOST-RESOURCES-MIB::hrSystemNumUsers.0"
# </Data>
# <Data "std_traffic">
# Type "if_octets"
# Table true
# InstancePrefix "traffic"
# Instance "IF-MIB::ifDescr"
# Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
# </Data>
#
# <Host "some.switch.mydomain.org">
# Address "192.168.0.2"
# Version 1
# Community "community_string"
# Collect "std_traffic"
# Inverval 120
# </Host>
# <Host "some.server.mydomain.org">
# Address "192.168.0.42"
# Version 2
# Community "another_string"
# Collect "std_traffic" "hr_users"
# </Host>
# <Host "some.ups.mydomain.org">
# Address "192.168.0.3"
# Version 1
# Community "more_communities"
# Collect "powerplus_voltge_input"
# Interval 300
# </Host>
#</Plugin>
#<Plugin swap>
# ReportByDevice false
#</Plugin>
#<Plugin table>
# <Table "/proc/slabinfo">
# Instance "slabinfo"
# Separator " "
# <Result>
# Type gauge
# InstancePrefix "active_objs"
# InstancesFrom 0
# ValuesFrom 1
# </Result>
# <Result>
# Type gauge
# InstancePrefix "objperslab"
# InstancesFrom 0
# ValuesFrom 4
# </Result>
# </Table>
#</Plugin>
#<Plugin "tail">
# <File "/var/log/exim4/mainlog">
# Instance "exim"
# <Match>
# Regex "S=([1-9][0-9]*)"
# DSType "CounterAdd"
# Type "ipt_bytes"
# Instance "total"
# </Match>
# <Match>
# Regex "\\<R=local_user\\>"
# ExcludeRegex "\\<R=local_user\\>.*mail_spool defer"
# DSType "CounterInc"
# Type "counter"
# Instance "local_user"
# </Match>
# </File>
#</Plugin>
<Plugin tcpconns>
LocalPort "4001"
LocalPort "7001"
</Plugin>
#<Plugin teamspeak2>
# Host "127.0.0.1"
# Port "51234"
# Server "8767"
#</Plugin>
#<Plugin ted>
# Device "/dev/ttyUSB0"
# Retries 0
#</Plugin>
#<Plugin thermal>
# ForceUseProcfs false
# Device "THRM"
# IgnoreSelected false
#</Plugin>
#<Plugin tokyotyrant>
# Host "localhost"
# Port "1978"
#</Plugin>
#<Plugin unixsock>
# SocketFile "/var/run/collectd-unixsock"
# SocketGroup "collectd"
# SocketPerms "0660"
# DeleteSocket false
#</Plugin>
#<Plugin uuid>
# UUIDFile "/etc/uuid"
#</Plugin>
#<Plugin varnish>
# <Instance>
# CollectCache true
# CollectBackend true
# CollectConnections true
# CollectSHM true
# CollectESI false
# CollectFetch false
# CollectHCB false
# CollectSMA false
# CollectSMS false
# CollectSM false
# CollectTotals false
# CollectWorkers false
# </Instance>
#
# <Instance "myinstance">
# CollectCache true
# </Instance>
#</Plugin>
#<Plugin vmem>
# Verbose false
#</Plugin>
#<Plugin write_graphite>
# <Carbon>
# Host "127.0.01"
# Port "2003"
# Prefix "collectd"
# Postfix "collectd"
# StoreRates false
# AlwaysAppendDS false
# EscapeCharacter "_"
# </Carbon>
#</Plugin>
#<Plugin write_http>
# <URL "http://example.com/collectd-post">
# User "collectd"
# Password "secret"
# VerifyPeer true
# VerifyHost true
# CACert "/etc/ssl/ca.crt"
# Format "Command"
# StoreRates false
# </URL>
#</Plugin>
#<Plugin write_mongodb>
# <Node "example">
# Host "localhost"
# Port "27017"
# Timeout 1000
# StoreRates false
# <Node>
#</Plugin>
Include "/etc/collectd/filters.conf"
Include "/etc/collectd/thresholds.conf"

View File

@ -0,0 +1,31 @@
from stackbrew/ubuntu:precise
run echo 'deb http://us.archive.ubuntu.com/ubuntu/ precise universe' >> /etc/apt/sources.list
run apt-get -y update
# Install required packages
run apt-get -y install python-cairo python-django python-twisted python-django-tagging python-simplejson python-pysqlite2 python-support python-pip gunicorn supervisor nginx-light
run pip install whisper
run pip install --install-option="--prefix=/var/lib/graphite" --install-option="--install-lib=/var/lib/graphite/lib" carbon
run pip install --install-option="--prefix=/var/lib/graphite" --install-option="--install-lib=/var/lib/graphite/webapp" graphite-web
# Add system service config
add ./nginx.conf /etc/nginx/nginx.conf
add ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Add graphite config
add ./initial_data.json /var/lib/graphite/webapp/graphite/initial_data.json
add ./local_settings.py /var/lib/graphite/webapp/graphite/local_settings.py
add ./carbon.conf /var/lib/graphite/conf/carbon.conf
add ./storage-schemas.conf /var/lib/graphite/conf/storage-schemas.conf
run mkdir -p /var/lib/graphite/storage/whisper
run touch /var/lib/graphite/storage/graphite.db /var/lib/graphite/storage/index
run chown -R www-data /var/lib/graphite/storage
run chmod 0775 /var/lib/graphite/storage /var/lib/graphite/storage/whisper
run chmod 0664 /var/lib/graphite/storage/graphite.db
run cd /var/lib/graphite/webapp/graphite && python manage.py syncdb --noinput
expose :80
expose :2003
cmd ["/usr/bin/supervisord"]

7
contrib/graphite/README Normal file
View File

@ -0,0 +1,7 @@
Running graphite under Docker is straightforward:
1. Build the graphite image using Docker
docker build -t graphite .
2. Run a graphite container. Be sure to replace the $IP field with the IP address at which you wish to expose your graphite web service.
docker run -p $IP:8080:80 -p $IP:2003:2003 -d graphite

View File

@ -0,0 +1,62 @@
[cache]
LOCAL_DATA_DIR = /var/lib/graphite/storage/whisper/
# Specify the user to drop privileges to
# If this is blank carbon runs as the user that invokes it
# This user must have write access to the local data directory
USER =
# Limit the size of the cache to avoid swapping or becoming CPU bound.
# Sorts and serving cache queries gets more expensive as the cache grows.
# Use the value "inf" (infinity) for an unlimited cache size.
MAX_CACHE_SIZE = inf
# Limits the number of whisper update_many() calls per second, which effectively
# means the number of write requests sent to the disk. This is intended to
# prevent over-utilizing the disk and thus starving the rest of the system.
# When the rate of required updates exceeds this, then carbon's caching will
# take effect and increase the overall throughput accordingly.
MAX_UPDATES_PER_SECOND = 1000
# Softly limits the number of whisper files that get created each minute.
# Setting this value low (like at 50) is a good way to ensure your graphite
# system will not be adversely impacted when a bunch of new metrics are
# sent to it. The trade off is that it will take much longer for those metrics'
# database files to all get created and thus longer until the data becomes usable.
# Setting this value high (like "inf" for infinity) will cause graphite to create
# the files quickly but at the risk of slowing I/O down considerably for a while.
MAX_CREATES_PER_MINUTE = inf
LINE_RECEIVER_INTERFACE = 0.0.0.0
LINE_RECEIVER_PORT = 2003
#PICKLE_RECEIVER_INTERFACE = 0.0.0.0
#PICKLE_RECEIVER_PORT = 2004
#CACHE_QUERY_INTERFACE = 0.0.0.0
#CACHE_QUERY_PORT = 7002
LOG_UPDATES = False
# Enable AMQP if you want to receve metrics using an amqp broker
# ENABLE_AMQP = False
# Verbose means a line will be logged for every metric received
# useful for testing
# AMQP_VERBOSE = False
# AMQP_HOST = localhost
# AMQP_PORT = 5672
# AMQP_VHOST = /
# AMQP_USER = guest
# AMQP_PASSWORD = guest
# AMQP_EXCHANGE = graphite
# Patterns for all of the metrics this machine will store. Read more at
# http://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol#Bindings
#
# Example: store all sales, linux servers, and utilization metrics
# BIND_PATTERNS = sales.#, servers.linux.#, #.utilization
#
# Example: store everything
# BIND_PATTERNS = #

View File

@ -0,0 +1,20 @@
[
{
"pk": 1,
"model": "auth.user",
"fields": {
"username": "admin",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"last_login": "2011-09-20 17:02:14",
"groups": [],
"user_permissions": [],
"password": "sha1$1b11b$edeb0a67a9622f1f2cfeabf9188a711f5ac7d236",
"email": "root@example.com",
"date_joined": "2011-09-20 17:02:14"
}
}
]

View File

@ -0,0 +1 @@
TIME_ZONE = 'UTC'

View File

@ -0,0 +1,69 @@
daemon off;
user www-data;
worker_processes 1;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
server_names_hash_bucket_size 32;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
server {
listen 80 default_server;
server_name _;
open_log_file_cache max=1000 inactive=20s min_uses=2 valid=1m;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Host $host;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
}
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Access-Control-Allow-Headers "origin, authorization, accept";
location /content {
alias /var/lib/graphite/webapp/content;
}
location /media {
alias /usr/share/pyshared/django/contrib/admin/media;
}
}
}

View File

@ -0,0 +1,7 @@
[carbon]
pattern = ^carbon\..*
retentions = 1m:31d,10m:1y,1h:5y
[default]
pattern = .*
retentions = 10s:8d,1m:31d,10m:1y,1h:5y

View File

@ -0,0 +1,25 @@
[supervisord]
nodaemon = true
environment = GRAPHITE_STORAGE_DIR='/var/lib/graphite/storage',GRAPHITE_CONF_DIR='/var/lib/graphite/conf'
[program:nginx]
command = /usr/sbin/nginx
stdout_logfile = /var/log/supervisor/%(program_name)s.log
stderr_logfile = /var/log/supervisor/%(program_name)s.log
autorestart = true
[program:carbon-cache]
user = www-data
command = /var/lib/graphite/bin/carbon-cache.py --debug start
stdout_logfile = /var/log/supervisor/%(program_name)s.log
stderr_logfile = /var/log/supervisor/%(program_name)s.log
autorestart = true
[program:graphite-webapp]
user = www-data
directory = /var/lib/graphite/webapp
environment = PYTHONPATH='/var/lib/graphite/webapp'
command = /usr/bin/gunicorn_django -b127.0.0.1:8000 -w2 graphite/settings.py
stdout_logfile = /var/log/supervisor/%(program_name)s.log
stderr_logfile = /var/log/supervisor/%(program_name)s.log
autorestart = true

145
discovery/discovery.go Normal file
View File

@ -0,0 +1,145 @@
package discovery
import (
"errors"
"fmt"
"net/url"
"path"
"strings"
"time"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/log"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
)
const (
stateKey = "_state"
startedState = "started"
defaultTTL = 604800 // One week TTL
)
type Discoverer struct {
client *etcd.Client
name string
peer string
prefix string
discoveryURL string
}
var defaultDiscoverer *Discoverer
func init() {
defaultDiscoverer = &Discoverer{}
}
func (d *Discoverer) Do(discoveryURL string, name string, peer string) (peers []string, err error) {
d.name = name
d.peer = peer
d.discoveryURL = discoveryURL
u, err := url.Parse(discoveryURL)
if err != nil {
return
}
// prefix is prepended to all keys for this discovery
d.prefix = strings.TrimPrefix(u.Path, "/v2/keys/")
// keep the old path in case we need to set the KeyPrefix below
oldPath := u.Path
u.Path = ""
// Connect to a scheme://host not a full URL with path
log.Infof("Discovery via %s using prefix %s.", u.String(), d.prefix)
d.client = etcd.NewClient([]string{u.String()})
if !strings.HasPrefix(oldPath, "/v2/keys") {
d.client.SetKeyPrefix("")
}
// Register this machine first and announce that we are a member of
// this cluster
err = d.heartbeat()
if err != nil {
return
}
// Start the very slow heartbeat to the cluster now in anticipation
// that everything is going to go alright now
go d.startHeartbeat()
// Attempt to take the leadership role, if there is no error we are it!
resp, err := d.client.Create(path.Join(d.prefix, stateKey), startedState, 0)
// Bail out on unexpected errors
if err != nil {
if clientErr, ok := err.(*etcd.EtcdError); !ok || clientErr.ErrorCode != etcdErr.EcodeNodeExist {
return nil, err
}
}
// If we got a response then the CAS was successful, we are leader
if resp != nil && resp.Node.Value == startedState {
// We are the leader, we have no peers
log.Infof("Discovery _state was empty, so this machine is the initial leader.")
return nil, nil
}
// Fall through to finding the other discovery peers
return d.findPeers()
}
func (d *Discoverer) findPeers() (peers []string, err error) {
resp, err := d.client.Get(path.Join(d.prefix), false, true)
if err != nil {
return nil, err
}
node := resp.Node
if node == nil {
return nil, fmt.Errorf("%s key doesn't exist.", d.prefix)
}
for _, n := range node.Nodes {
// Skip our own entry in the list, there is no point
if strings.HasSuffix(n.Key, "/"+d.name) {
continue
}
peers = append(peers, n.Value)
}
if len(peers) == 0 {
return nil, errors.New("Discovery found an initialized cluster but no peers are registered.")
}
log.Infof("Discovery found peers %v", peers)
return
}
func (d *Discoverer) startHeartbeat() {
// In case of errors we should attempt to heartbeat fairly frequently
heartbeatInterval := defaultTTL / 8
ticker := time.Tick(time.Second * time.Duration(heartbeatInterval))
for {
select {
case <-ticker:
err := d.heartbeat()
if err != nil {
log.Warnf("Discovery heartbeat failed: %v", err)
}
}
}
}
func (d *Discoverer) heartbeat() error {
_, err := d.client.Set(path.Join(d.prefix, d.name), d.peer, defaultTTL)
return err
}
func Do(discoveryURL string, name string, peer string) ([]string, error) {
return defaultDiscoverer.Do(discoveryURL, name, peer)
}

View File

@ -33,12 +33,18 @@ const (
EcodeNodeExist = 105
EcodeKeyIsPreserved = 106
EcodeRootROnly = 107
EcodeDirNotEmpty = 108
EcodeValueRequired = 200
EcodePrevValueRequired = 201
EcodeTTLNaN = 202
EcodeIndexNaN = 203
EcodeValueOrTTLRequired = 204
EcodeValueRequired = 200
EcodePrevValueRequired = 201
EcodeTTLNaN = 202
EcodeIndexNaN = 203
EcodeValueOrTTLRequired = 204
EcodeTimeoutNaN = 205
EcodeNameRequired = 206
EcodeIndexOrValueRequired = 207
EcodeIndexValueMutex = 208
EcodeInvalidField = 209
EcodeRaftInternal = 300
EcodeLeaderElect = 301
@ -51,14 +57,15 @@ func init() {
errors = make(map[int]string)
// command related errors
errors[EcodeKeyNotFound] = "Key Not Found"
errors[EcodeTestFailed] = "Test Failed" //test and set
errors[EcodeNotFile] = "Not A File"
errors[EcodeKeyNotFound] = "Key not found"
errors[EcodeTestFailed] = "Compare failed" //test and set
errors[EcodeNotFile] = "Not a file"
errors[EcodeNoMorePeer] = "Reached the max number of peers in the cluster"
errors[EcodeNotDir] = "Not A Directory"
errors[EcodeNodeExist] = "Already exists" // create
errors[EcodeNotDir] = "Not a directory"
errors[EcodeNodeExist] = "Key already exists" // create
errors[EcodeRootROnly] = "Root is read only"
errors[EcodeKeyIsPreserved] = "The prefix of given key is a keyword in etcd"
errors[EcodeDirNotEmpty] = "Directory not empty"
// Post form related errors
errors[EcodeValueRequired] = "Value is Required in POST form"
@ -66,6 +73,11 @@ func init() {
errors[EcodeTTLNaN] = "The given TTL in POST form is not a number"
errors[EcodeIndexNaN] = "The given index in POST form is not a number"
errors[EcodeValueOrTTLRequired] = "Value or TTL is required in POST form"
errors[EcodeTimeoutNaN] = "The given timeout in POST form is not a number"
errors[EcodeNameRequired] = "Name is required in POST form"
errors[EcodeIndexOrValueRequired] = "Index or value is required"
errors[EcodeIndexValueMutex] = "Index and value cannot both be specified"
errors[EcodeInvalidField] = "Invalid field"
// raft related errors
errors[EcodeRaftInternal] = "Raft Internal Error"
@ -109,10 +121,19 @@ func (e Error) toJsonString() string {
func (e Error) Write(w http.ResponseWriter) {
w.Header().Add("X-Etcd-Index", fmt.Sprint(e.Index))
// 3xx is reft internal error
if e.ErrorCode/100 == 3 {
http.Error(w, e.toJsonString(), http.StatusInternalServerError)
} else {
http.Error(w, e.toJsonString(), http.StatusBadRequest)
// 3xx is raft internal error
status := http.StatusBadRequest
switch e.ErrorCode {
case EcodeKeyNotFound:
status = http.StatusNotFound
case EcodeNotFile, EcodeDirNotEmpty:
status = http.StatusForbidden
case EcodeTestFailed, EcodeNodeExist:
status = http.StatusPreconditionFailed
default:
if e.ErrorCode/100 == 3 {
status = http.StatusInternalServerError
}
}
http.Error(w, e.toJsonString(), status)
}

147
etcd.go
View File

@ -18,17 +18,26 @@ package main
import (
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
"time"
"github.com/coreos/etcd/third_party/github.com/coreos/raft"
"github.com/coreos/etcd/config"
ehttp "github.com/coreos/etcd/http"
"github.com/coreos/etcd/log"
"github.com/coreos/etcd/metrics"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/store"
"github.com/coreos/raft"
)
func main() {
// Load configuration.
var config = server.NewConfig()
var config = config.New()
if err := config.Load(os.Args[1:]); err != nil {
fmt.Println(server.Usage() + "\n")
fmt.Println(err.Error() + "\n")
@ -42,7 +51,10 @@ func main() {
}
// Enable options.
if config.VeryVerbose {
if config.VeryVeryVerbose {
log.Verbose = true
raft.SetLogLevel(raft.Trace)
} else if config.VeryVerbose {
log.Verbose = true
raft.SetLogLevel(raft.Debug)
} else if config.Verbose {
@ -61,42 +73,129 @@ func main() {
log.Fatalf("Unable to create path: %s", err)
}
// Load info object.
info, err := config.Info()
if err != nil {
log.Fatal("info:", err)
// Warn people if they have an info file
info := filepath.Join(config.DataDir, "info")
if _, err := os.Stat(info); err == nil {
log.Warnf("All cached configuration is now ignored. The file %s can be removed.", info)
}
// Retrieve TLS configuration.
tlsConfig, err := info.EtcdTLS.Config()
if err != nil {
log.Fatal("Client TLS:", err)
var mbName string
if config.Trace() {
mbName = config.MetricsBucketName()
runtime.SetBlockProfileRate(1)
}
peerTLSConfig, err := info.RaftTLS.Config()
mb := metrics.NewBucket(mbName)
if config.GraphiteHost != "" {
err := mb.Publish(config.GraphiteHost)
if err != nil {
panic(err)
}
}
// Retrieve CORS configuration
corsInfo, err := ehttp.NewCORSInfo(config.CorsOrigins)
if err != nil {
log.Fatal("Peer TLS:", err)
log.Fatal("CORS:", err)
}
// Create etcd key-value store and registry.
store := store.New()
registry := server.NewRegistry(store)
// Create peer server.
ps := server.NewPeerServer(info.Name, config.DataDir, info.RaftURL, info.RaftListenHost, &peerTLSConfig, &info.RaftTLS, registry, store, config.SnapshotCount)
ps.MaxClusterSize = config.MaxClusterSize
ps.RetryTimes = config.MaxRetryAttempts
// Create stats objects
followersStats := server.NewRaftFollowersStats(config.Name)
serverStats := server.NewRaftServerStats(config.Name)
// Create client server.
s := server.New(info.Name, info.EtcdURL, info.EtcdListenHost, &tlsConfig, &info.EtcdTLS, ps, registry, store)
if err := s.AllowOrigins(config.CorsOrigins); err != nil {
panic(err)
// Calculate all of our timeouts
heartbeatTimeout := time.Duration(config.Peer.HeartbeatTimeout) * time.Millisecond
electionTimeout := time.Duration(config.Peer.ElectionTimeout) * time.Millisecond
dialTimeout := (3 * heartbeatTimeout) + electionTimeout
responseHeaderTimeout := (3 * heartbeatTimeout) + electionTimeout
// Create peer server
psConfig := server.PeerServerConfig{
Name: config.Name,
Scheme: config.PeerTLSInfo().Scheme(),
URL: config.Peer.Addr,
SnapshotCount: config.SnapshotCount,
MaxClusterSize: config.MaxClusterSize,
RetryTimes: config.MaxRetryAttempts,
RetryInterval: config.RetryInterval,
}
ps := server.NewPeerServer(psConfig, registry, store, &mb, followersStats, serverStats)
var psListener net.Listener
if psConfig.Scheme == "https" {
peerServerTLSConfig, err := config.PeerTLSInfo().ServerConfig()
if err != nil {
log.Fatal("peer server TLS error: ", err)
}
psListener, err = server.NewTLSListener(config.Peer.BindAddr, peerServerTLSConfig)
if err != nil {
log.Fatal("Failed to create peer listener: ", err)
}
} else {
psListener, err = server.NewListener(config.Peer.BindAddr)
if err != nil {
log.Fatal("Failed to create peer listener: ", err)
}
}
// Create raft transporter and server
raftTransporter := server.NewTransporter(followersStats, serverStats, registry, heartbeatTimeout, dialTimeout, responseHeaderTimeout)
if psConfig.Scheme == "https" {
raftClientTLSConfig, err := config.PeerTLSInfo().ClientConfig()
if err != nil {
log.Fatal("raft client TLS error: ", err)
}
raftTransporter.SetTLSConfig(*raftClientTLSConfig)
}
raftServer, err := raft.NewServer(config.Name, config.DataDir, raftTransporter, store, ps, "")
if err != nil {
log.Fatal(err)
}
raftServer.SetElectionTimeout(electionTimeout)
raftServer.SetHeartbeatInterval(heartbeatTimeout)
ps.SetRaftServer(raftServer)
// Create etcd server
s := server.New(config.Name, config.Addr, ps, registry, store, &mb)
if config.Trace() {
s.EnableTracing()
}
var sListener net.Listener
if config.EtcdTLSInfo().Scheme() == "https" {
etcdServerTLSConfig, err := config.EtcdTLSInfo().ServerConfig()
if err != nil {
log.Fatal("etcd TLS error: ", err)
}
sListener, err = server.NewTLSListener(config.BindAddr, etcdServerTLSConfig)
if err != nil {
log.Fatal("Failed to create TLS etcd listener: ", err)
}
} else {
sListener, err = server.NewListener(config.BindAddr)
if err != nil {
log.Fatal("Failed to create etcd listener: ", err)
}
}
ps.SetServer(s)
ps.Start(config.Snapshot, config.Peers)
// Run peer server in separate thread while the client server blocks.
go func() {
log.Fatal(ps.ListenAndServe(config.Snapshot, config.Peers))
log.Infof("peer server [name %s, listen on %s, advertised url %s]", ps.Config.Name, psListener.Addr(), ps.Config.URL)
sHTTP := &ehttp.CORSHandler{ps.HTTPHandler(), corsInfo}
log.Fatal(http.Serve(psListener, sHTTP))
}()
log.Fatal(s.ListenAndServe())
log.Infof("etcd server [name %s, listen on %s, advertised url %s]", s.Name, sListener.Addr(), s.URL())
sHTTP := &ehttp.CORSHandler{s.HTTPHandler(), corsInfo}
log.Fatal(http.Serve(sListener, sHTTP))
}

View File

@ -1,3 +1,3 @@
// +build !go1.1
// +build !go1.2
"etcd requires go 1.1 or greater to build"
"etcd requires go 1.2 or greater to build"

View File

@ -14,56 +14,55 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package server
package http
import (
"fmt"
"net/http"
"net/url"
"github.com/gorilla/mux"
)
type corsHandler struct {
router *mux.Router
corsOrigins map[string]bool
}
type CORSInfo map[string]bool
// AllowOrigins sets a comma-delimited list of origins that are allowed.
func (s *corsHandler) AllowOrigins(origins []string) error {
func NewCORSInfo(origins []string) (*CORSInfo, error) {
// Construct a lookup of all origins.
m := make(map[string]bool)
for _, v := range origins {
if v != "*" {
if _, err := url.Parse(v); err != nil {
return fmt.Errorf("Invalid CORS origin: %s", err)
return nil, fmt.Errorf("Invalid CORS origin: %s", err)
}
}
m[v] = true
}
s.corsOrigins = m
return nil
info := CORSInfo(m)
return &info, nil
}
// OriginAllowed determines whether the server will allow a given CORS origin.
func (c *corsHandler) OriginAllowed(origin string) bool {
return c.corsOrigins["*"] || c.corsOrigins[origin]
func (c CORSInfo) OriginAllowed(origin string) bool {
return c["*"] || c[origin]
}
type CORSHandler struct {
Handler http.Handler
Info *CORSInfo
}
// addHeader adds the correct cors headers given an origin
func (h *corsHandler) addHeader(w http.ResponseWriter, origin string) {
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)
}
// ServeHTTP adds the correct CORS headers based on the origin and returns immediatly
// with a 200 OK if the method is OPTIONS.
func (h *corsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
func (h *CORSHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Write CORS header.
if h.OriginAllowed("*") {
if h.Info.OriginAllowed("*") {
h.addHeader(w, "*")
} else if origin := req.Header.Get("Origin"); h.OriginAllowed(origin) {
} else if origin := req.Header.Get("Origin"); h.Info.OriginAllowed(origin) {
h.addHeader(w, origin)
}
@ -72,7 +71,5 @@ func (h *corsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
h.router.ServeHTTP(w, req)
h.Handler.ServeHTTP(w, req)
}

36
http/query_params.go Normal file
View File

@ -0,0 +1,36 @@
package http
import (
"net/http"
"strings"
)
func NewLowerQueryParamsHandler(hdlr http.Handler) *LowerQueryParamsHandler {
return &LowerQueryParamsHandler{hdlr}
}
type LowerQueryParamsHandler struct {
Handler http.Handler
}
func (h *LowerQueryParamsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if err == nil {
lowerBoolQueryParams(req)
}
h.Handler.ServeHTTP(w, req)
}
func lowerBoolQueryParams(req *http.Request) {
form := req.Form
for key, vals := range form {
for i, val := range vals {
lowered := strings.ToLower(val)
if lowered == "true" || lowered == "false" {
req.Form[key][i] = lowered
} else {
req.Form[key][i] = val
}
}
}
}

46
http/query_params_test.go Normal file
View File

@ -0,0 +1,46 @@
package http
import (
"net/http"
"testing"
)
type NilResponseWriter struct{}
func (w NilResponseWriter) Header() http.Header {
return http.Header{}
}
func (w NilResponseWriter) Write(data []byte) (int, error) {
return 0, nil
}
func (w NilResponseWriter) WriteHeader(code int) {
return
}
type FunctionHandler struct {
f func(*http.Request)
}
func (h FunctionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.f(r)
}
func TestQueryParamsLowered(t *testing.T) {
assertFunc := func(req *http.Request) {
if len(req.Form["One"]) != 1 || req.Form["One"][0] != "true" {
t.Errorf("Unexpected value for One: %s", req.Form["One"])
} else if len(req.Form["TWO"]) != 1 || req.Form["TWO"][0] != "false" {
t.Errorf("Unexpected value for TWO")
} else if len(req.Form["three"]) != 2 || req.Form["three"][0] != "true" || req.Form["three"][1] != "false" {
t.Errorf("Unexpected value for three")
}
}
assertHdlr := FunctionHandler{assertFunc}
hdlr := NewLowerQueryParamsHandler(assertHdlr)
respWriter := NilResponseWriter{}
req, _ := http.NewRequest("GET", "http://example.com?One=TRUE&TWO=False&three=true&three=FALSE", nil)
hdlr.ServeHTTP(respWriter, req)
}

View File

@ -1,7 +1,7 @@
package log
import (
golog "github.com/coreos/go-log/log"
golog "github.com/coreos/etcd/third_party/github.com/coreos/go-log/log"
"os"
)

42
metrics/metrics.go Normal file
View File

@ -0,0 +1,42 @@
// Package metrics provides both a means of generating metrics and the ability
// to send metric data to a graphite endpoint.
// The usage of this package without providing a graphite_addr when calling
// NewBucket results in NOP metric objects. No data will be collected.
package metrics
import (
"io"
gometrics "github.com/coreos/etcd/third_party/github.com/rcrowley/go-metrics"
)
type Timer gometrics.Timer
type Gauge gometrics.Gauge
type Bucket interface {
// If a timer exists in this Bucket, return it. Otherwise, create
// a new timer with the given name and store it in this Bucket.
// The returned object will fulfull the Timer interface.
Timer(name string) Timer
// This acts similarly to Timer, but with objects that fufill the
// Gauge interface.
Gauge(name string) Gauge
// Write the current state of all Metrics in a human-readable format
// to the provide io.Writer.
Dump(io.Writer)
// Instruct the Bucket to periodically push all metric data to the
// provided graphite endpoint.
Publish(string) error
}
// Create a new Bucket object that periodically
func NewBucket(name string) Bucket {
if name == "" {
return nilBucket{}
}
return newStandardBucket(name)
}

25
metrics/nil.go Normal file
View File

@ -0,0 +1,25 @@
package metrics
import (
"io"
gometrics "github.com/coreos/etcd/third_party/github.com/rcrowley/go-metrics"
)
type nilBucket struct{}
func (nmb nilBucket) Dump(w io.Writer) {
return
}
func (nmb nilBucket) Timer(name string) Timer {
return gometrics.NilTimer{}
}
func (nmf nilBucket) Gauge(name string) Gauge {
return gometrics.NilGauge{}
}
func (nmf nilBucket) Publish(string) error {
return nil
}

86
metrics/standard.go Normal file
View File

@ -0,0 +1,86 @@
package metrics
import (
"io"
"net"
"sync"
"time"
gometrics "github.com/coreos/etcd/third_party/github.com/rcrowley/go-metrics"
)
const (
// RuntimeMemStatsSampleInterval is the interval in seconds at which the
// Go runtime's memory statistics will be gathered.
RuntimeMemStatsSampleInterval = time.Duration(2) * time.Second
// GraphitePublishInterval is the interval in seconds at which all
// gathered statistics will be published to a Graphite endpoint.
GraphitePublishInterval = time.Duration(2) * time.Second
)
type standardBucket struct {
sync.Mutex
name string
registry gometrics.Registry
timers map[string]Timer
gauges map[string]Gauge
}
func newStandardBucket(name string) standardBucket {
registry := gometrics.NewRegistry()
gometrics.RegisterRuntimeMemStats(registry)
go gometrics.CaptureRuntimeMemStats(registry, RuntimeMemStatsSampleInterval)
return standardBucket{
name: name,
registry: registry,
timers: make(map[string]Timer),
gauges: make(map[string]Gauge),
}
}
func (smb standardBucket) Dump(w io.Writer) {
gometrics.WriteOnce(smb.registry, w)
return
}
func (smb standardBucket) Timer(name string) Timer {
smb.Lock()
defer smb.Unlock()
timer, ok := smb.timers[name]
if !ok {
timer = gometrics.NewTimer()
smb.timers[name] = timer
smb.registry.Register(name, timer)
}
return timer
}
func (smb standardBucket) Gauge(name string) Gauge {
smb.Lock()
defer smb.Unlock()
gauge, ok := smb.gauges[name]
if !ok {
gauge = gometrics.NewGauge()
smb.gauges[name] = gauge
smb.registry.Register(name, gauge)
}
return gauge
}
func (smb standardBucket) Publish(graphite_addr string) error {
addr, err := net.ResolveTCPAddr("tcp", graphite_addr)
if err != nil {
return err
}
go gometrics.Graphite(smb.registry, GraphitePublishInterval, smb.name, addr)
return nil
}

View File

@ -174,7 +174,7 @@ module.exports = function (grunt) {
options: {
dest: '<%= yeoman.dist %>'
},
html: ['<%= yeoman.app %>/**/*.html']
html: ['<%= yeoman.app %>/index.html']
},
usemin: {
options: {
@ -240,6 +240,14 @@ module.exports = function (grunt) {
}]
}
},
ngmin: {
dist: {
src: '.tmp/concat/scripts/app.js',
dest: '.tmp/concat/scripts/app.js'
}
},
// Put files not handled in other tasks here
copy: {
dist: {
@ -251,10 +259,10 @@ module.exports = function (grunt) {
src: [
'*.{ico,png,txt}',
'.htaccess',
'images/{,*/}*.{webp,gif}',
'images/{,*/}*.{webp,gif,svg}',
'styles/fonts/{,*/}*.*',
'views/*.*',
'index.html',
//'index.html',
'bower_components/sass-bootstrap/fonts/*.*'
]
}]
@ -286,7 +294,7 @@ module.exports = function (grunt) {
'copy:styles'
],
dist: [
'compass',
//'compass',
'copy:styles',
'imagemin',
'svgmin',
@ -327,13 +335,15 @@ module.exports = function (grunt) {
grunt.registerTask('build', [
'clean:dist',
'jshint',
'useminPrepare',
'concurrent:dist',
'autoprefixer',
'concat',
'cssmin',
'uglify',
'ngmin',
'usemin',
'uglify',
'copy:dist'
]);

View File

@ -22,6 +22,8 @@ bower install
### View in Browser
run `export ETCD_DASHBOARD_DIR=/absolute/path/to/coreos/etcd/mod/dashboard/app`
Run etcd like you normally would and afterward browse to:
http://localhost:4001/mod/dashboard/

View File

@ -1 +0,0 @@
*.coffee

View File

@ -1,50 +0,0 @@
<!doctype html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>etcd Browser</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css(.tmp) styles/main.css -->
<link rel="stylesheet" href="styles/etcd-widgets.css">
<link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,400italic,600,700,900" rel="stylesheet" type="text/css">
<link href="http://fonts.googleapis.com/css?family=Source+Code+Pro:400,500,600,700" rel="stylesheet" type="text/css">
<!-- endbuild -->
</head>
<body ng-app="etcdBrowser">
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<!--[if lt IE 9]>
<script src="bower_components/es5-shim/es5-shim.js"></script>
<script src="bower_components/json3/lib/json3.min.js"></script>
<![endif]-->
<!-- Add your site or application content here -->
<div id="etd_browser" ng-view="etcd">
</div>
<!-- build:js scripts/browser-modules.js -->
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/underscore/underscore.js"></script>
<script src="bower_components/moment/moment.js"></script>
<!-- endbuild -->
<!-- build:js({.tmp,app}) scripts/browser-scripts.js -->
<script src="scripts/ng-time-relative.min.js"></script>
<script src="scripts/common/services/etcd.js"></script>
<script src="scripts/controllers/browser.js"></script>
<!-- endbuild -->
</body>
</html>

View File

@ -0,0 +1,7 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
preserveAspectRatio="xMinYMin" viewBox="0 0 72.556 61" enable-background="new 0 0 72.556 61" xml:space="preserve">
<path d="M34.521,8v11.088v23v10.737c0,2.209,1.791,4,4,4c2.209,0,4-1.791,4-4V42.067V19.109V8c0-2.209-1.791-4-4-4
C36.312,4,34.521,5.791,34.521,8z"/>
<path d="M16.109,34.412h11.088h23h10.737c2.209,0,4-1.791,4-4c0-2.209-1.791-4-4-4H50.175H27.217H16.109c-2.209,0-4,1.791-4,4
C12.109,32.621,13.9,34.412,16.109,34.412z"/>
</svg>

After

Width:  |  Height:  |  Size: 561 B

View File

@ -0,0 +1,6 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
preserveAspectRatio="xMinYMin" viewBox="0 0 73.356 61" enable-background="new 0 0 73.356 61" xml:space="preserve">
<path d="M5.27,33.226l22.428,22.428c1.562,1.562,4.095,1.562,5.657,0c1.562-1.562,1.562-4.095,0-5.657L17.77,34.413h48.514
c2.209,0,4-1.791,4-4s-1.791-4-4-4H17.749l15.604-15.582c1.563-1.561,1.565-4.094,0.004-5.657C32.576,4.391,31.552,4,30.527,4
c-1.023,0-2.046,0.39-2.827,1.169L5.272,27.567c-0.751,0.75-1.173,1.768-1.173,2.829C4.098,31.458,4.52,32.476,5.27,33.226z"/>
</svg>

After

Width:  |  Height:  |  Size: 608 B

View File

@ -0,0 +1,7 @@
<svg version="1.1" fill="#f00" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" preserveAspectRatio="xMinYMin" viewBox="0 0 76.143 61" enable-background="new 0 0 76.143 61" xml:space="preserve">
<path d="M49.41,13.505l-6.035,6.035L27.112,35.803l-6.035,6.035c-1.562,1.562-1.562,4.095,0,5.657c1.562,1.562,4.095,1.562,5.657,0
l6.05-6.05l16.234-16.234l6.05-6.05c1.562-1.562,1.562-4.095,0-5.657C53.505,11.943,50.972,11.943,49.41,13.505z"/>
<path d="M21.077,19.162l6.035,6.035L43.375,41.46l6.035,6.035c1.562,1.562,4.095,1.562,5.657,0c1.562-1.562,1.562-4.095,0-5.657
l-6.05-6.05L32.783,19.555l-6.05-6.05c-1.562-1.562-4.095-1.562-5.657,0C19.515,15.067,19.515,17.6,21.077,19.162z"/>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@ -0,0 +1,46 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
preserveAspectRatio="xMinYMin" viewBox="0 0 792 306" enable-background="new 0 0 792 306" xml:space="preserve">
<g>
<g>
<path fill="#53A3DA" d="M136.168,45.527C76.898,45.527,28.689,93.739,28.689,153c0,59.265,48.209,107.474,107.479,107.474
c59.252,0,107.465-48.209,107.465-107.474C243.633,93.739,195.42,45.527,136.168,45.527z"/>
<path fill="#F1606D" d="M136.168,55.389c-17.283,0-31.941,27.645-37.235,66.069c-0.169,1.236-0.333,2.487-0.478,3.746
c-0.723,6.047-1.213,12.335-1.458,18.808c-0.117,2.962-0.175,5.956-0.175,8.988c0,3.029,0.058,6.029,0.175,8.985
c0.245,6.472,0.735,12.764,1.458,18.811c8.104,1.049,16.769,1.761,25.807,2.099c3.907,0.146,7.872,0.233,11.907,0.233
c4.023,0,8-0.088,11.895-0.233c9.049-0.338,17.708-1.05,25.819-2.099c0.892-0.114,1.77-0.239,2.659-0.368
c33.754-4.74,57.235-15.232,57.235-27.428C233.776,99.088,190.071,55.389,136.168,55.389z"/>
<path fill="#FFFFFF" d="M176.541,125.569c-0.979-1.428-2.029-2.796-3.148-4.11c-8.956-10.557-22.297-17.265-37.224-17.265
c-4.839,0-9.148,7.407-11.907,18.909c-1.096,4.586-1.947,9.819-2.495,15.498c-0.432,4.551-0.665,9.391-0.665,14.399
s0.233,9.849,0.665,14.396c4.554,0.432,9.387,0.664,14.402,0.664c5.009,0,9.842-0.232,14.396-0.664
c10.011-0.95,18.653-2.875,24.775-5.411c6.046-2.501,9.624-5.615,9.624-8.985C184.963,142.832,181.858,133.388,176.541,125.569z"
/>
</g>
<g>
<path fill="#231F20" d="M344.891,100.053c12.585,0,22.816,6.138,29.262,13.062l-10.064,11.326
c-5.353-5.192-11.175-8.495-19.041-8.495c-16.839,0-28.953,14.16-28.953,37.291c0,23.448,11.169,37.608,28.32,37.608
c9.128,0,15.895-3.775,21.717-10.228l10.067,11.169c-8.335,9.598-19.038,14.95-32.099,14.95c-26.119,0-46.731-18.88-46.731-53.025
C297.37,120.036,318.454,100.053,344.891,100.053z"/>
<path fill="#231F20" d="M416.961,125.701c19.352,0,36.822,14.793,36.822,40.597c0,25.647-17.471,40.439-36.822,40.439
c-19.197,0-36.66-14.792-36.66-40.439C380.301,140.494,397.764,125.701,416.961,125.701z M416.961,191.945
c11.33,0,18.25-10.228,18.25-25.647c0-15.577-6.92-25.804-18.25-25.804s-18.094,10.227-18.094,25.804
C398.867,181.717,405.631,191.945,416.961,191.945z"/>
<path fill="#231F20" d="M459.771,127.589h14.943l1.26,13.688h0.629c5.506-10.07,13.691-15.577,21.871-15.577
c3.938,0,6.455,0.472,8.811,1.574l-3.148,15.734c-2.67-0.784-4.717-1.257-8.018-1.257c-6.139,0-13.539,4.245-18.256,15.893v47.203
h-18.092V127.589z"/>
<path fill="#231F20" d="M541.121,125.701c20.928,0,31.941,15.107,31.941,36.667c0,3.458-0.314,6.604-0.787,8.495h-49.09
c1.57,14.003,10.379,21.869,22.811,21.869c6.613,0,12.273-2.041,17.941-5.662l6.135,11.326
c-7.395,4.878-16.676,8.341-26.432,8.341c-21.404,0-38.08-14.95-38.08-40.439C505.561,141.12,523.023,125.701,541.121,125.701z
M557.326,159.376c0-12.277-5.189-19.671-15.732-19.671c-9.125,0-16.996,6.768-18.57,19.671H557.326z"/>
<path fill="#F1606D" d="M600.602,152.607c0-32.729,17.785-53.344,42.799-53.344c24.863,0,42.641,20.615,42.641,53.344
c0,32.889-17.777,54.13-42.641,54.13C618.387,206.737,600.602,185.496,600.602,152.607z M678.49,152.607
c0-28.639-14.158-46.731-35.09-46.731c-21.084,0-35.248,18.093-35.248,46.731c0,28.796,14.164,47.521,35.248,47.521
C664.332,200.128,678.49,181.403,678.49,152.607z"/>
<path fill="#53A4D9" d="M699.738,186.125c7.557,8.495,18.412,14.003,30.529,14.003c15.732,0,25.807-8.499,25.807-20.767
c0-12.904-8.494-17.154-18.723-21.717l-15.736-7.082c-8.969-3.936-20.934-10.385-20.934-25.808
c0-14.947,12.904-25.492,30.059-25.492c12.588,0,22.658,5.665,28.949,12.435l-4.244,4.878c-5.982-6.452-14.32-10.7-24.705-10.7
c-13.691,0-22.816,7.239-22.816,18.565c0,11.962,10.385,16.521,17.936,19.985l15.738,6.921
c11.486,5.195,21.713,11.647,21.713,27.539s-13.061,27.851-33.201,27.851c-15.107,0-26.75-6.451-34.932-15.576L699.738,186.125z"
/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -1,8 +1,6 @@
<!doctype html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<!DOCTYPE html>
<html class="no-js" ng-app="etcdControlPanel" ng-controller="RootCtrl">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -11,124 +9,56 @@
<meta name="viewport" content="width=device-width">
<link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,400italic,600,700,900" rel="stylesheet" type="text/css">
<link href="http://fonts.googleapis.com/css?family=Source+Code+Pro:400,500,600,700" rel="stylesheet" type="text/css">
<style>
body {
padding: 30px;
margin: 0px;
}
h1 {
font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 400;
margin: 0px 0px 20px 0px;
padding: 0px;
}
iframe {
border: none;
}
<!-- build:css styles/styles.css -->
<link rel="stylesheet" href="styles/bootstrap.css">
<link rel="stylesheet" href="styles/main.css">
<link rel="stylesheet" href="styles/browser.css">
<link rel="stylesheet" href="styles/stats.css">
<link rel="stylesheet" href="styles/etcd-widgets.css">
<!-- endbuild -->
</head>
a {
color: #1e6ec1;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
iframe {
margin-bottom: 30px;
}
iframe + iframe {
margin-bottom: 0px;
}
#footer {
width: 100%;
font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
margin-top: 20px;
}
#coreos-logo {
margin: 10px auto 0 auto;
height: 30px;
width: 80px;
}
#coreos-logo svg {
fill: #999;
max-width: 100px;
display: inline-block;
vertical-align: middle;
}
#powered-by {
font-size: 12px;
color: #333;
width: 100%;
display: inline-block;
vertical-align: middle;
line-height: 190%;
text-align: center;
}
</style>
</head>
<body>
<body>
<h1>etcd Dashboard</h1>
<iframe src="stats.html" style="width: 100%; height: 400px;"></iframe>
<iframe src="browser.html" style="width: 100%; height: 400px;"></iframe>
<div id="view-container" ng-view></div>
<div id="footer">
<div id="powered-by">Powered by <a href="https://github.com/coreos/etcd">etcd</a></div>
<div id="coreos-logo">
<a href="http://coreos.com">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
preserveAspectRatio="xMinYMin" viewBox="0 0 792 306" enable-background="new 0 0 792 306" xml:space="preserve">
<g>
<g>
<path fill="#53A3DA" d="M136.168,45.527C76.898,45.527,28.689,93.739,28.689,153c0,59.265,48.209,107.474,107.479,107.474
c59.252,0,107.465-48.209,107.465-107.474C243.633,93.739,195.42,45.527,136.168,45.527z"/>
<path fill="#F1606D" d="M136.168,55.389c-17.283,0-31.941,27.645-37.235,66.069c-0.169,1.236-0.333,2.487-0.478,3.746
c-0.723,6.047-1.213,12.335-1.458,18.808c-0.117,2.962-0.175,5.956-0.175,8.988c0,3.029,0.058,6.029,0.175,8.985
c0.245,6.472,0.735,12.764,1.458,18.811c8.104,1.049,16.769,1.761,25.807,2.099c3.907,0.146,7.872,0.233,11.907,0.233
c4.023,0,8-0.088,11.895-0.233c9.049-0.338,17.708-1.05,25.819-2.099c0.892-0.114,1.77-0.239,2.659-0.368
c33.754-4.74,57.235-15.232,57.235-27.428C233.776,99.088,190.071,55.389,136.168,55.389z"/>
<path fill="#FFFFFF" d="M176.541,125.569c-0.979-1.428-2.029-2.796-3.148-4.11c-8.956-10.557-22.297-17.265-37.224-17.265
c-4.839,0-9.148,7.407-11.907,18.909c-1.096,4.586-1.947,9.819-2.495,15.498c-0.432,4.551-0.665,9.391-0.665,14.399
s0.233,9.849,0.665,14.396c4.554,0.432,9.387,0.664,14.402,0.664c5.009,0,9.842-0.232,14.396-0.664
c10.011-0.95,18.653-2.875,24.775-5.411c6.046-2.501,9.624-5.615,9.624-8.985C184.963,142.832,181.858,133.388,176.541,125.569z"
/>
</g>
<g>
<path fill="#231F20" d="M344.891,100.053c12.585,0,22.816,6.138,29.262,13.062l-10.064,11.326
c-5.353-5.192-11.175-8.495-19.041-8.495c-16.839,0-28.953,14.16-28.953,37.291c0,23.448,11.169,37.608,28.32,37.608
c9.128,0,15.895-3.775,21.717-10.228l10.067,11.169c-8.335,9.598-19.038,14.95-32.099,14.95c-26.119,0-46.731-18.88-46.731-53.025
C297.37,120.036,318.454,100.053,344.891,100.053z"/>
<path fill="#231F20" d="M416.961,125.701c19.352,0,36.822,14.793,36.822,40.597c0,25.647-17.471,40.439-36.822,40.439
c-19.197,0-36.66-14.792-36.66-40.439C380.301,140.494,397.764,125.701,416.961,125.701z M416.961,191.945
c11.33,0,18.25-10.228,18.25-25.647c0-15.577-6.92-25.804-18.25-25.804s-18.094,10.227-18.094,25.804
C398.867,181.717,405.631,191.945,416.961,191.945z"/>
<path fill="#231F20" d="M459.771,127.589h14.943l1.26,13.688h0.629c5.506-10.07,13.691-15.577,21.871-15.577
c3.938,0,6.455,0.472,8.811,1.574l-3.148,15.734c-2.67-0.784-4.717-1.257-8.018-1.257c-6.139,0-13.539,4.245-18.256,15.893v47.203
h-18.092V127.589z"/>
<path fill="#231F20" d="M541.121,125.701c20.928,0,31.941,15.107,31.941,36.667c0,3.458-0.314,6.604-0.787,8.495h-49.09
c1.57,14.003,10.379,21.869,22.811,21.869c6.613,0,12.273-2.041,17.941-5.662l6.135,11.326
c-7.395,4.878-16.676,8.341-26.432,8.341c-21.404,0-38.08-14.95-38.08-40.439C505.561,141.12,523.023,125.701,541.121,125.701z
M557.326,159.376c0-12.277-5.189-19.671-15.732-19.671c-9.125,0-16.996,6.768-18.57,19.671H557.326z"/>
<path fill="#F1606D" d="M600.602,152.607c0-32.729,17.785-53.344,42.799-53.344c24.863,0,42.641,20.615,42.641,53.344
c0,32.889-17.777,54.13-42.641,54.13C618.387,206.737,600.602,185.496,600.602,152.607z M678.49,152.607
c0-28.639-14.158-46.731-35.09-46.731c-21.084,0-35.248,18.093-35.248,46.731c0,28.796,14.164,47.521,35.248,47.521
C664.332,200.128,678.49,181.403,678.49,152.607z"/>
<path fill="#53A4D9" d="M699.738,186.125c7.557,8.495,18.412,14.003,30.529,14.003c15.732,0,25.807-8.499,25.807-20.767
c0-12.904-8.494-17.154-18.723-21.717l-15.736-7.082c-8.969-3.936-20.934-10.385-20.934-25.808
c0-14.947,12.904-25.492,30.059-25.492c12.588,0,22.658,5.665,28.949,12.435l-4.244,4.878c-5.982-6.452-14.32-10.7-24.705-10.7
c-13.691,0-22.816,7.239-22.816,18.565c0,11.962,10.385,16.521,17.936,19.985l15.738,6.921
c11.486,5.195,21.713,11.647,21.713,27.539s-13.061,27.851-33.201,27.851c-15.107,0-26.75-6.451-34.932-15.576L699.738,186.125z"
/>
</g>
</g>
</svg>
</a>
</div>
<div id="powered-by" class="text-center">Powered by <a href="https://github.com/coreos/etcd" tabindex="-1">etcd</a></div>
<div id="coreos-logo">
<a href="http://coreos.com" tabindex="-1"><img src="images/logo.svg"/></a>
</div>
</div>
</body>
<!-- build:js scripts/modules.js -->
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/d3/d3.js"></script>
<script src="bower_components/underscore/underscore.js"></script>
<script src="bower_components/underscore.string/lib/underscore.string.js"></script>
<script src="bower_components/moment/moment.js"></script>
<script src="scripts/vega.js"></script>
<script src="scripts/ng-time-relative.min.js"></script>
<!-- endbuild -->
<!-- build:js scripts/app.js -->
<script src="scripts/app.js"></script>
<script src="scripts/controllers/root.js"></script>
<script src="scripts/directives.js"></script>
<script src="scripts/shims.js"></script>
<script src="scripts/controllers/home.js"></script>
<script src="scripts/controllers/browser.js"></script>
<script src="scripts/common/services/etcd.js"></script>
<script src="scripts/common/services/prefix-url.js"></script>
<script src="scripts/common/directives/highlight.js"></script>
<script src="scripts/common/directives/enter.js"></script>
<script src="scripts/common/services/etcd.js"></script>
<script src="scripts/controllers/stats.js"></script>
<!-- endbuild -->
</body>
</html>

View File

@ -0,0 +1,43 @@
'use strict';
var app = angular.module('etcdControlPanel', [
'ngRoute',
'ngResource',
'etcd',
'etcdDirectives',
'timeRelative',
'underscore',
'jquery',
'moment',
'vg'
]);
app.constant('urlPrefix', '/mod/dashboard');
app.constant('keyPrefix', '/v2/keys/');
app.config(function($routeProvider, $locationProvider, urlPrefix) {
function prefixUrl(url) {
return urlPrefix + url;
}
$locationProvider.html5Mode(true);
$routeProvider
.when(prefixUrl('/'), {
controller: 'HomeCtrl',
templateUrl: prefixUrl('/views/home.html')
})
.when(prefixUrl('/stats'), {
controller: 'StatsCtrl',
templateUrl: prefixUrl('/views/stats.html')
})
.when(prefixUrl('/browser'), {
controller: 'BrowserCtrl',
templateUrl: prefixUrl('/views/browser.html')
})
.otherwise({
templateUrl: prefixUrl('/404.html')
});
});

View File

@ -0,0 +1,16 @@
'use strict';
angular.module('etcdControlPanel')
.directive('ngEnter', function() {
return function(scope, element, attrs) {
element.bind('keydown keypress', function(event) {
if(event.which === 13) {
scope.$apply(function(){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});

View File

@ -0,0 +1,19 @@
'use strict';
angular.module('etcdControlPanel')
.directive('highlight', function(keyPrefix) {
return {
restrict: 'E',
scope: {
highlightBase: '=',
highlightCurrent: '='
},
link: function(scope, element, attrs) {
var base = _.str.strRight(scope.highlightBase, keyPrefix),
current = _.str.trim(scope.highlightCurrent, '/');
if (base === current) {
element.parent().parent().addClass('etcd-selected');
}
}
};
});

View File

@ -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;
});
});

View File

@ -0,0 +1,10 @@
'use strict';
angular.module('etcdControlPanel')
.factory('prefixUrl', function(urlPrefix) {
return function(url) {
return urlPrefix + url;
}
});

View File

@ -1,33 +1,39 @@
'use strict';
angular.module('etcdBrowser', ['ngRoute', 'etcd', 'timeRelative'])
.constant('keyPrefix', '/v2/keys/')
.config(['$routeProvider', 'keyPrefix', function ($routeProvider, keyPrefix) {
//read localstorage
var previousPath = localStorage.getItem('etcd_path');
$routeProvider
.when('/', {
redirectTo: keyPrefix
})
.otherwise({
templateUrl: 'views/browser.html',
controller: 'MainCtrl'
});
}])
.controller('MainCtrl', ['$scope', '$location', 'EtcdV2', 'keyPrefix', function ($scope, $location, EtcdV2, keyPrefix) {
angular.module('etcdControlPanel')
.controller('BrowserCtrl', function ($scope, $window, EtcdV2, keyPrefix, $, _, moment) {
$scope.save = 'etcd-save-hide';
$scope.preview = 'etcd-preview-hide';
$scope.enableBack = true;
$scope.writingNew = false;
$scope.key = null;
$scope.list = [];
// etcdPath is the path to the key that is currenly being looked at.
$scope.etcdPath = $location.path();
$scope.etcdPath = keyPrefix;
$scope.inputPath = keyPrefix;
$scope.$watch('etcdPath', function() {
$scope.resetInputPath = function() {
$scope.inputPath = $scope.etcdPath;
};
$scope.setActiveKey = function(key) {
$scope.etcdPath = keyPrefix + _.str.trim(key, '/');
$scope.resetInputPath();
};
$scope.stripPrefix = function(path) {
return _.str.strRight(path, keyPrefix);
};
$scope.onEnter = function() {
var path = $scope.stripPrefix($scope.inputPath);
if (path !== '') {
$scope.setActiveKey(path);
}
};
$scope.updateCurrentKey = function() {
function etcdPathKey() {
return pathKey($scope.etcdPath);
}
@ -39,17 +45,17 @@ angular.module('etcdBrowser', ['ngRoute', 'etcd', 'timeRelative'])
}
return parts[1];
}
// Notify everyone of the update
localStorage.setItem('etcdPath', $scope.etcdPath);
$scope.enableBack = true;
//disable back button if at root (/v2/keys/)
if($scope.etcdPath === keyPrefix) {
if ($scope.etcdPath === keyPrefix) {
$scope.enableBack = false;
}
$scope.key = EtcdV2.getKey(etcdPathKey($scope.etcdPath));
});
};
$scope.$watch('etcdPath', $scope.updateCurrentKey);
$scope.$watch('key', function() {
if ($scope.writingNew === true) {
@ -60,13 +66,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.singleValue = data.node.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.';
@ -79,20 +85,18 @@ angular.module('etcdBrowser', ['ngRoute', 'etcd', 'timeRelative'])
//back button click
$scope.back = function() {
$scope.etcdPath = $scope.key.getParent().path();
$scope.syncLocation();
$scope.resetInputPath();
$scope.preview = 'etcd-preview-hide';
$scope.writingNew = false;
};
$scope.syncLocation = function() {
$location.path($scope.etcdPath);
};
$scope.showSave = function() {
$scope.save = 'etcd-save-reveal';
};
$scope.saveData = function() {
$scope.setActiveKey($scope.stripPrefix($scope.inputPath));
$scope.updateCurrentKey();
// TODO: fixup etcd to allow for empty values
$scope.key.set($scope.singleValue || ' ').then(function(response) {
$scope.save = 'etcd-save-hide';
@ -100,11 +104,13 @@ angular.module('etcdBrowser', ['ngRoute', 'etcd', 'timeRelative'])
$scope.back();
$scope.writingNew = false;
}, function (response) {
$scope.showSaveError(data.message);
$scope.showSaveError(response.message);
});
};
$scope.deleteKey = function() {
$scope.deleteKey = function(key) {
$scope.setActiveKey(key);
$scope.updateCurrentKey();
$scope.key.deleteKey().then(function(response) {
//TODO: remove loader
$scope.save = 'etcd-save-hide';
@ -136,56 +142,15 @@ angular.module('etcdBrowser', ['ngRoute', 'etcd', 'timeRelative'])
};
$scope.getHeight = function() {
return $(window).height();
return $($window).height();
};
$scope.$watch($scope.getHeight, function() {
$('.etcd-body').css('height', $scope.getHeight()-45);
});
window.onresize = function(){
//$scope.$watch($scope.getHeight, function() {
////$('.etcd-container.etcd-browser etcd-body').css('height', $scope.getHeight()-45);
//});
$window.onresize = function(){
$scope.$apply();
};
}])
.directive('ngEnter', function() {
return function(scope, element, attrs) {
element.bind('keydown keypress', function(event) {
if(event.which === 13) {
scope.$apply(function(){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
})
.directive('highlight', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
if('#' + scope.etcdPath === attrs.href) {
element.parent().parent().addClass('etcd-selected');
}
}
};
});
moment.lang('en', {
relativeTime : {
future: 'Expires in %s',
past: 'Expired %s ago',
s: 'seconds',
m: 'a minute',
mm: '%d minutes',
h: 'an hour',
hh: '%d hours',
d: 'a day',
dd: '%d days',
M: 'a month',
MM: '%d months',
y: 'a year',
yy: '%d years'
}
});

View File

@ -0,0 +1,3 @@
angular.module('etcdControlPanel')
.controller('HomeCtrl', function($scope) {
});

View File

@ -0,0 +1,9 @@
'use strict';
angular.module('etcdControlPanel')
.controller('RootCtrl', function($rootScope, prefixUrl) {
// Expose prefixUrl() function to all.
$rootScope.prefixUrl = prefixUrl;
});

View File

@ -1,20 +1,8 @@
'use strict';
angular.module('etcdStats', ['ngRoute', 'etcd'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/stats.html',
controller: 'StatsCtrl'
})
.otherwise({
templateUrl: 'views/stats.html',
controller: 'StatsCtrl'
});
}])
.controller('StatsCtrl', ['$scope', 'EtcdV2', 'statsVega', function ($scope, EtcdV2, statsVega) {
angular.module('etcdControlPanel')
.controller('StatsCtrl', function ($scope, $rootScope, $interval, EtcdV2, statsVega, vg) {
$scope.graphContainer = '#latency';
$scope.graphVisibility = 'etcd-graph-show';
$scope.tableVisibility = 'etcd-table-hide';
@ -42,10 +30,14 @@ angular.module('etcdStats', ['ngRoute', 'etcd'])
});
//sort array so peers don't jump when output
$scope.peers.sort(function(a, b){
if(a.name < b.name) return -1;
if(a.name > b.name) return 1;
if(a.name < b.name) {
return -1;
}
if(a.name > b.name) {
return 1;
}
return 0;
});
});
drawGraph();
});
}
@ -86,10 +78,10 @@ angular.module('etcdStats', ['ngRoute', 'etcd'])
$scope.getWidth = function() {
return $(window).width();
};
$scope.$watch($scope.getHeight, function() {
$('.etcd-body').css('height', $scope.getHeight()-5);
readStats();
});
//$scope.$watch($scope.getHeight, function() {
////$('.etcd-container.etcd-stats .etcd-body').css('height', $scope.getHeight()-5);
//readStats();
//});
$scope.$watch($scope.getWidth, function() {
readStats();
});
@ -97,12 +89,31 @@ angular.module('etcdStats', ['ngRoute', 'etcd'])
$scope.$apply();
};
// Update the graphs live
setInterval(function() {
readStats();
$scope.$apply();
}, 500);
}])
$scope.pollPromise = null;
$scope.startPolling = function() {
// Update the graphs live
if ($scope.pollPromise) {
return;
}
$scope.pollPromise = $interval(function() {
readStats();
}, 500);
};
$scope.stopPolling = function() {
$interval.cancel($scope.pollPromise);
$scope.pollPromise = null;
};
// Stop polling when navigating away from a view with this controller.
$rootScope.$on('$routeChangeStart', function () {
$scope.stopPolling();
});
$scope.startPolling();
})
/* statsVega returns the vega configuration for the stats dashboard */

View File

@ -0,0 +1,3 @@
'use strict';
angular.module('etcdDirectives', []);

View File

@ -0,0 +1,36 @@
'use strict';
angular.module('underscore', []).factory('_', function($window) {
return $window._;
});
angular.module('jquery', []).factory('$', function($window) {
return $window.$;
});
angular.module('vg', []).factory('vg', function($window) {
return $window.vg;
});
angular.module('moment', []).factory('moment', function($window) {
$window.moment.lang('en', {
relativeTime : {
future: 'Expires in %s',
past: 'Expired %s ago',
s: 'seconds',
m: 'a minute',
mm: '%d minutes',
h: 'an hour',
hh: '%d hours',
d: 'a day',
dd: '%d days',
M: 'a month',
MM: '%d months',
y: 'a year',
yy: '%d years'
}
});
return $window.moment;
});

View File

@ -1,50 +0,0 @@
<!doctype html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>etcd Browser</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css(.tmp) styles/main.css -->
<link rel="stylesheet" href="styles/etcd-widgets.css">
<link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,400italic,600,700,900" rel="stylesheet" type="text/css">
<link href="http://fonts.googleapis.com/css?family=Source+Code+Pro:400,500,600,700" rel="stylesheet" type="text/css">
<!-- endbuild -->
</head>
<body ng-app="etcdStats">
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<!--[if lt IE 9]>
<script src="bower_components/es5-shim/es5-shim.js"></script>
<script src="bower_components/json3/lib/json3.min.js"></script>
<![endif]-->
<!-- Add your site or application content here -->
<div id="etd_stats" ng-view="etcd">
</div>
<!-- build:js scripts/stats-modules.js -->
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-cookies/angular-cookies.js"></script>
<script src="bower_components/angular-sanitize/angular-sanitize.js"></script>
<script src="bower_components/d3/d3.js"></script>
<script src="bower_components/underscore/underscore.js"></script>
<!-- endbuild -->
<!-- build:js({.tmp,app}) scripts/stats-scripts.js -->
<script src="scripts/vega.js"></script>
<script src="scripts/common/services/etcd.js"></script>
<script src="scripts/controllers/stats.js"></script>
<!-- endbuild -->
</body>
</html>

View File

@ -0,0 +1,186 @@
.etcd-container.etcd-browser {
width: 100%;
height: 500px;
}
.home-container .etcd-container.etcd-browser {
height: 400px;
}
.etcd-container.etcd-browser .etcd-header {
height: 37px;
}
.etcd-container.etcd-browser.etcd-preview-reveal .etcd-back {
display: block;
}
.etcd-container.etcd-browser.etcd-preview-hide .etcd-back {
display: block;
}
.etcd-container.etcd-browser.etcd-preview-reveal .etcd-add {
}
.etcd-container.etcd-browser.etcd-preview-hide .etcd-add {
}
.etcd-container.etcd-browser .etcd-header .etcd-browser-path {
position: absolute;
left: 72px;
right: 0px;
top: 0;
margin: 6px 5px 6px 5px;
}
.etcd-container.etcd-browser .etcd-header .etcd-browser-path input {
width: 100%;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.etcd-container.etcd-browser .etcd-header .etcd-save {
position: absolute;
width: 54px;
right: -55px;
margin: 6px 0;
}
.etcd-container.etcd-browser.etcd-save-reveal .etcd-header .etcd-save {
right: 7px;
}
.etcd-container.etcd-browser.etcd-save-reveal .etcd-header .etcd-browser-path {
right: 62px;
}
.etcd-container.etcd-browser.etcd-save-hide .etcd-header .etcd-save {
right: -55px;
}
.etcd-container.etcd-browser.etcd-save-hide .etcd-header .etcd-browser-path {
right: 0px;
}
.etcd-container.etcd-browser .etcd-preview {
position: absolute;
left: 100%;
min-height: 100%;
overflow-y: auto;
overflow-x: hidden;
top: 0px;
box-sizing: border-box;
-moz-box-sizing: border-box;
background-color: #fff;
width: 100%;
border-left: 1px solid #ddd;
}
.etcd-container.etcd-browser .etcd-preview pre, .etcd-container.etcd-browser .etcd-preview textarea {
padding: 20px 20px 20px 20px;
margin: 0px;
font-family: Consolas, "Liberation Mono", Courier, monospace;
height: 100%;
width: 100%;
white-space: pre-wrap;
position: absolute;
font-size: 13px;
border: 1px;
outline: none;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview pre, .etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview textarea {
display: block;
}
.etcd-container.etcd-browser .etcd-preview .etcd-empty {
top: 0px;
bottom: 0px;
width: 100%;
text-align: center;
position: absolute;
}
.etcd-container.etcd-browser.etcd-preview-reveal .etcd-empty {
display: none;
}
.etcd-container.etcd-browser .etcd-preview .etcd-empty-message {
margin-top: 25%;
color: #999;
}
/* Single Column Positioning */
@media (max-width: 700px) {
.etcd-container.etcd-browser .etcd-list {
width: 100%;
}
.etcd-container.etcd-browser.etcd-preview-reveal .etcd-list {
left: -100%;
transition-property: all;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
}
.etcd-container.etcd-browser.etcd-preview-hide .etcd-list {
left: 0%;
transition-property: all;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
}
.etcd-container.etcd-browser .etcd-preview {
left: 100%;
}
.etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview { left: -1px;
transition-property: all;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
}
.etcd-container.etcd-browser.etcd-preview-hide .etcd-preview {
left: 100%;
transition-property: all;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
}
}
/* Double Column Positioning */
@media (min-width: 700px) {
.etcd-container.etcd-browser .etcd-list {
width: 50%;
}
.etcd-container.etcd-browser .etcd-preview {
left: 50%;
width: 50%;
}
.etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview {
left: 50%; /* does nothing */
}
.etcd-container.etcd-browser.etcd-preview-reveal .etcd-preview .etcd-empty {
display: none;
}
.etcd-container.etcd-browser.etcd-preview-hide .etcd-preview {
left: 50%; /* does nothing */
}
.etcd-container.etcd-browser.etcd-preview-hide .etcd-preview .etcd-empty {
display: block;
}
.etcd-container.etcd-browser.etcd-preview-hide .etcd-preview pre, .etcd-container.etcd-browser.etcd-preview-hide .etcd-preview textarea {
display: none;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +1,56 @@
html {
height: 100%;
}
body {
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #333;
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #333;
padding: 30px;
margin: 0px;
height: 100%;
}
h1 {
font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 400;
margin: 0px 0px 20px 0px;
padding: 0px;
}
.hero-unit {
margin: 50px auto 0 auto;
width: 300px;
font-size: 18px;
font-weight: 200;
line-height: 30px;
background-color: #eee;
border-radius: 6px;
padding: 60px;
a {
color: #1e6ec1;
text-decoration: none;
}
.hero-unit h1 {
font-size: 60px;
line-height: 1;
letter-spacing: -1px;
a:hover {
text-decoration: underline;
}
#footer {
width: 100%;
font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
margin-top: 20px;
}
#coreos-logo {
margin: 10px auto 0 auto;
height: 30px;
width: 80px;
}
#coreos-logo svg {
fill: #999;
MAX-WIDTH: 100PX;
DISPLAY: INLINE-BLOCK;
VERTICAL-ALIGN: MIDDLE;
}
#POWERED-BY {
FONT-SIZE: 12PX;
COLOR: #333;
WIDTH: 100%;
DISPLAY: INLINE-BLOCK;
VERTICAL-ALIGN: MIDDLE;
LINE-HEIGHT: 190%;
TEXT-ALIGN: CENTER;
}

View File

@ -0,0 +1,144 @@
.etcd-container.etcd-stats {
width: 100%;
height: 500px;
}
.home-container .etcd-container.etcd-stats {
height: 400px;
}
.etcd-container.etcd-stats h2 {
margin-top: -7px;
}
.etcd-container.etcd-stats table .etcd-latency {
width: 50%;
}
.etcd-container.etcd-stats .etcd-list {
position: absolute;
left: 100%;
min-height: 100%;
overflow-y: auto;
overflow-x: hidden;
top: 0px;
box-sizing: border-box;
-moz-box-sizing: border-box;
background-color: #fff;
width: 100%;
border-left: 1px solid #ddd;
}
.etcd-container.etcd-stats .etcd-list .etcd-square {
height: 10px;
width: 10px;
display: inline-block;
margin-right: 5px;
}
.etcd-container.etcd-stats .etcd-list .etcd-square-red {
background-color: #c40022;
}
.etcd-container.etcd-stats .etcd-list .etcd-square-orange {
background-color: #FFC000;
}
.etcd-container.etcd-stats .etcd-list .etcd-square-green {
background-color: #00DB24;
}
.etcd-container.etcd-stats .etcd-list .etcd-peer-type {
color: #999;
padding-left: 3px;
font-size: 13px;
line-height: 100%;
}
.etcd-container.etcd-stats .etcd-list .etcd-latency-value {
display: inline-block;
}
.etcd-container.etcd-stats .etcd-graph {
box-sizing: border-box;
-moz-box-sizing: border-box;
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
padding: 20px;
}
.etcd-container.etcd-stats .etcd-graph .etcd-graph-container {
position: absolute;
top: 60px;
bottom: 20px;
left: 20px;
right: 20px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
/* Single Column Positioning */
@media (max-width: 700px) {
.etcd-container.etcd-stats .etcd-list {
width: 100%;
left: 100%;
}
.etcd-container.etcd-stats .etcd-graph {
left: 0%;
}
.etcd-container.etcd-stats.etcd-table-reveal .etcd-graph {
left: -100%;
transition-property: all;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
}
.etcd-container.etcd-stats.etcd-table-hide .etcd-graph {
left: 0%;
transition-property: all;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
}
.etcd-container.etcd-stats.etcd-table-hide .etcd-format-selector .etcd-selector-graph svg * {
fill: #428bca;
}
.etcd-container.etcd-stats.etcd-table-hide .etcd-list {
left: 100%;
transition-property: all;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
}
.etcd-container.etcd-stats.etcd-table-reveal .etcd-list {
left: 0%;
transition-property: all;
transition-duration: 250ms;
transition-timing-function: ease-in-out;
}
.etcd-container.etcd-stats.etcd-table-reveal .etcd-format-selector .etcd-selector-table svg * {
fill: #428bca;
}
}
/* Double Column Positioning */
@media (min-width: 700px) {
.etcd-container.etcd-stats .etcd-list {
width: 50%;
left: 50%;
}
.etcd-container.etcd-stats .etcd-graph {
left: 0%;
width: 50%;
}
.etcd-container.etcd-stats .etcd-format-selector {
display: none;
}
}

View File

@ -1,99 +1,73 @@
<div class="etcd-container etcd-browser {{columns}} {{preview}} {{save}}">
<!--
<div class="etcd-popover etcd-popover-error">
<div class="etcd-popover-notch"></div>
<div class="etcd-popover-content">
Overwrite this value?
</div>
<div class="etcd-popover-confirm">
<button class="etcd-button etcd-button-small etcd-button-primary etcd-confirm">Overwrite</button>
</div>
</div>
-->
<div class="etcd-popover etcd-popover-error" id="etcd-save-error">
<div class="etcd-popover-notch"></div>
<div class="etcd-popover-content">
Error:
</div>
</div>
<div class="etcd-popover etcd-popover-error" id="etcd-browse-error">
<div class="etcd-popover-notch"></div>
<div class="etcd-popover-content">
Error:
</div>
</div>
<div class="etcd-header solid">
<a class="etcd-back" ng-click="back()" ng-class="{false:'etcd-disabled'}[enableBack]">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
preserveAspectRatio="xMinYMin" viewBox="0 0 73.356 61" enable-background="new 0 0 73.356 61" xml:space="preserve">
<path d="M5.27,33.226l22.428,22.428c1.562,1.562,4.095,1.562,5.657,0c1.562-1.562,1.562-4.095,0-5.657L17.77,34.413h48.514
c2.209,0,4-1.791,4-4s-1.791-4-4-4H17.749l15.604-15.582c1.563-1.561,1.565-4.094,0.004-5.657C32.576,4.391,31.552,4,30.527,4
c-1.023,0-2.046,0.39-2.827,1.169L5.272,27.567c-0.751,0.75-1.173,1.768-1.173,2.829C4.098,31.458,4.52,32.476,5.27,33.226z"/>
</svg>
</a>
<a class="etcd-add">
<div ng-controller="BrowserCtrl" class="etcd-container etcd-browser {{columns}} {{preview}} {{save}}">
<svg version="1.1" ng-click="add()" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
preserveAspectRatio="xMinYMin" viewBox="0 0 72.556 61" enable-background="new 0 0 72.556 61" xml:space="preserve">
<path d="M34.521,8v11.088v23v10.737c0,2.209,1.791,4,4,4c2.209,0,4-1.791,4-4V42.067V19.109V8c0-2.209-1.791-4-4-4
C36.312,4,34.521,5.791,34.521,8z"/>
<path d="M16.109,34.412h11.088h23h10.737c2.209,0,4-1.791,4-4c0-2.209-1.791-4-4-4H50.175H27.217H16.109c-2.209,0-4,1.791-4,4
C12.109,32.621,13.9,34.412,16.109,34.412z"/>
</svg>
</a>
<div class="etcd-browser-path">
<input type="text" ng-model="etcdPath" ng-enter="syncLocation()" tabindex="888" />
</div>
<button class="etcd-button etcd-button-small etcd-button-primary etcd-save" ng-click="saveData()">Save</button>
<div class="etcd-popover etcd-popover-error" id="etcd-save-error">
<div class="etcd-popover-notch"></div>
<div class="etcd-popover-content">Error:</div>
</div>
<div class="etcd-popover etcd-popover-error" id="etcd-browse-error">
<div class="etcd-popover-notch"></div>
<div class="etcd-popover-content">Error:</div>
</div>
<div class="etcd-header solid">
<a class="etcd-back" ng-click="back()" ng-class="{false:'etcd-disabled'}[enableBack]">
<img src="images/back.svg"/>
</a>
<a class="etcd-add" ng-click="add()"><img src="images/add.svg"/></a>
<div class="etcd-browser-path">
<input type="text" ng-model="inputPath" ng-enter="onEnter()" tabindex="888" />
</div>
<div class="etcd-body">
<div class="etcd-list">
<table cellpadding="0" cellspacing="0">
<thead>
<td class="etcd-name-header">Name</td>
<td class="etcd-ttl-header">TTL</td>
<td class="etcd-actions-header">&nbsp</td>
</thead>
<tbody>
<tr ng-repeat="key in list | orderBy:'key'">
<td><a ng-class="{true:'directory'}[key.dir]" href="#/v2/keys{{key.key}}" highlight>{{key.key}}</a></td>
<td ng-switch on="!!key.expiration" class="etcd-ttl">
<div ng-switch-when="true"><time relative datetime="{{key.expiration.substring(0, key.expiration.lastIndexOf('-'))}}"></time></div>
<div ng-switch-default class="etcd-ttl-none">&mdash;</div>
</td>
<td>
<div class="etcd-actions">
<div class="etcd-delete" ng-switch on="!!key.dir">
<svg ng-switch-when="false" ng-click="deleteKey()" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" preserveAspectRatio="xMinYMin" viewBox="0 0 76.143 61" enable-background="new 0 0 76.143 61" xml:space="preserve">
<path d="M49.41,13.505l-6.035,6.035L27.112,35.803l-6.035,6.035c-1.562,1.562-1.562,4.095,0,5.657c1.562,1.562,4.095,1.562,5.657,0
l6.05-6.05l16.234-16.234l6.05-6.05c1.562-1.562,1.562-4.095,0-5.657C53.505,11.943,50.972,11.943,49.41,13.505z"/>
<path d="M21.077,19.162l6.035,6.035L43.375,41.46l6.035,6.035c1.562,1.562,4.095,1.562,5.657,0c1.562-1.562,1.562-4.095,0-5.657
l-6.05-6.05L32.783,19.555l-6.05-6.05c-1.562-1.562-4.095-1.562-5.657,0C19.515,15.067,19.515,17.6,21.077,19.162z"/>
</svg>
<div ng-switch-when="true"></div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="etcd-preview">
<textarea placeholder="Enter a key name above and the value here" ng-model="singleValue" tabindex="888" ng-change="showSave()">
</textarea>
<div class="etcd-empty">
<div class="etcd-empty-message">{{preview_message}}</div>
<button class="etcd-button etcd-button-small etcd-button-primary etcd-save" ng-click="saveData()">Save</button>
</div>
<div class="etcd-body">
<div class="etcd-list">
<table cellpadding="0" cellspacing="0">
<thead>
<td class="etcd-name-header">Name</td>
<td class="etcd-ttl-header">TTL</td>
<td class="etcd-actions-header">&nbsp;</td>
</thead>
<tbody>
<tr ng-repeat="key in list | orderBy:'key'">
<td>
<highlight ng-class="{true:'directory'}[key.dir]" ng-click="setActiveKey(key.key)" highlight-base="etcdPath" highlight-current="key.key">{{key.key}}</highlight>
</td>
<td ng-switch on="!!key.expiration" class="etcd-ttl">
<div ng-switch-when="true"><time relative datetime="{{key.expiration.substring(0, key.expiration.lastIndexOf('-'))}}"></time></div>
<div ng-switch-default class="etcd-ttl-none">&mdash;</div>
</td>
<td>
<div class="etcd-actions">
<div ng-switch on="!!key.dir">
<img class="etcd-delete" src="images/delete.svg" ng-switch-when="false" ng-click="deleteKey(key.key)" />
<div ng-switch-when="true"></div>
</div>
</div>
<div class="etcd-dialog">
<div class="etcd-dialog-message">
Save and replicate this change?
</div>
<div class="etcd-dialog-buttons">
<button class="etcd-button etcd-button-primary">Save Changes</button>
<a href="javascript:void(0);">Cancel</a>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="etcd-preview">
<textarea placeholder="Enter a key name above and the value here" ng-model="singleValue" tabindex="888" ng-change="showSave()"></textarea>
<div class="etcd-empty">
<div class="etcd-empty-message">{{preview_message}}</div>
</div>
<div class="etcd-dialog">
<div class="etcd-dialog-message">Save and replicate this change?</div>
<div class="etcd-dialog-buttons">
<button class="etcd-button etcd-button-primary">Save Changes</button>
<a href="javascript:void(0);">Cancel</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,4 @@
<a href="stats" tabindex="-1">stats only</a>
<div ng-include="prefixUrl('/views/stats.html')" class="home-container"></div>
<a href="browser" tabindex="-1">browser only</a>
<div ng-include="prefixUrl('/views/browser.html')" class="home-container"></div>

View File

@ -1,4 +1,4 @@
<div class="etcd-container etcd-stats {{columns}} {{tableVisibility}}">
<div ng-controller="StatsCtrl" class="etcd-container etcd-stats {{columns}} {{tableVisibility}}">
<div class="etcd-body">
<div class="etcd-format-selector">
<div class="etcd-selector-item etcd-selector-graph" ng-click="showGraph()">

View File

@ -12,11 +12,11 @@
"angular-cookies": "~1.2.0-rc.2",
"angular-sanitize": "~1.2.0-rc.2",
"d3": "~3.3.6",
"moment": "~2.3.0"
"moment": "~2.3.0",
"underscore.string": "~2.3.3"
},
"devDependencies": {
"angular-mocks": "~1.2.0-rc.2",
"angular-scenario": "~1.2.0-rc.2",
"underscore": "~1.5.2"
}
}

View File

@ -8,10 +8,11 @@ npm install
bower install
grunt build
export GOPATH="${DIR}/../../"
go get github.com/jteeuwen/go-bindata/...
for i in `find dist -type f`; do
file=$(echo $i | sed 's#dist/##g' | sed 's#/#-#g')
go build github.com/jteeuwen/go-bindata
./go-bindata -nomemcopy -pkg "resources" -toc -out resources/$file.go -prefix dist $i
dirs=
for i in `find dist -type d`; do
dirs="${dirs} ${i}"
done
${GOPATH}/bin/go-bindata -nomemcopy -pkg "resources" -o resources/resources.go -prefix dist ${dirs}

View File

@ -24,9 +24,9 @@ func memoryFileServer(w http.ResponseWriter, req *http.Request) {
file = file + ".html"
}
upath = path.Join(dir, file)
b, ok := resources.File("/" + upath)
b, err := resources.Asset(upath)
if ok == false {
if err != nil {
http.Error(w, upath+": File not found", http.StatusNotFound)
return
}

View File

@ -11,19 +11,17 @@
"grunt-contrib-compass": "~0.5.0",
"grunt-contrib-jshint": "~0.6.0",
"grunt-contrib-cssmin": "~0.6.0",
"grunt-contrib-connect": "~0.3.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-htmlmin": "~0.1.3",
"grunt-contrib-imagemin": "~0.2.0",
"grunt-contrib-watch": "~0.5.2",
"grunt-autoprefixer": "~0.2.0",
"grunt-usemin": "~0.1.11",
"grunt-usemin": "~2.0.2",
"grunt-svgmin": "~0.2.0",
"grunt-rev": "~0.1.0",
"grunt-open": "~0.2.0",
"grunt-concurrent": "~0.3.0",
"load-grunt-tasks": "~0.1.0",
"connect-livereload": "~0.2.0",
"grunt-google-cdn": "~0.2.0",
"grunt-ngmin": "~0.0.2",
"time-grunt": "~0.1.0",

View File

@ -1,11 +0,0 @@
package resources
func File(name string) ([]byte, bool) {
data, ok := go_bindata[name]
if ok == false {
return nil, false
}
return data(), true
}

View File

@ -1,7 +0,0 @@
package resources
// Global Table of Contents map. Generated by go-bindata.
// After startup of the program, all generated data files will
// put themselves in this map. The key is the full filename, as
// supplied to go-bindata.
var go_bindata = make(map[string]func() []byte)

View File

@ -1,39 +0,0 @@
package resources
import (
"bytes"
"compress/gzip"
"io"
"reflect"
"unsafe"
)
var _browser_html = "\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8c\x54\x4f\x6f\xdb\x3e\x0c\xbd\xf7\x53\xb0\xbe\xcb\x3e\xfc\xf0\x43\xdb\x41\x36\xd0\x0d\x3d\xf4\xb6\xcb\x80\x0d\x45\x51\x28\x12\x13\xab\xb3\x45\x4d\xa2\x93\xe5\xdb\x4f\xf2\x9f\xd6\x6d\xb3\x21\x39\xc4\xb4\xc8\xf7\x44\xf2\xbd\x44\x5e\x1a\xd2\x7c\xf4\x08\x2d\xf7\x5d\x73\x21\x2f\x85\x78\xb0\x5b\xe8\x18\xee\xef\xe0\xea\xb1\x81\xf1\x23\x73\x16\x74\xa7\x62\xac\x0b\x47\xe2\x39\xa6\x0a\x61\xf1\x66\x7a\x5c\x4f\x8f\xab\xa2\x01\x79\xf9\x80\xce\xd8\xed\xa3\x10\xaf\x6c\x6b\xaa\x33\xd8\xfe\x41\x73\x7d\x0e\xcd\xdf\xf0\x3b\x9e\x29\xf2\x41\x73\x02\x3f\x02\x85\x78\x03\xce\xf7\xa0\x32\x39\x48\x61\x8f\xac\x40\xb7\x2a\x44\xe4\xba\x18\x78\x2b\x52\xb7\xab\x54\xcb\xec\x05\xfe\x1a\xec\xbe\x2e\xbe\x8b\x6f\xb7\xe2\x0b\xf5\x5e\xb1\xdd\x74\x58\x80\x26\xc7\xe8\x12\xee\xfe\xae\x46\xb3\xc3\x05\xc9\x96\x3b\x6c\x90\xb5\x81\xcf\x81\x0e\x11\x83\xac\xa6\xb3\x15\xb3\x53\x3d\xd6\x85\xc1\xa8\x83\xf5\x6c\xc9\xad\xf8\x8a\x8f\x85\x7b\x8b\x07\x4f\x81\x57\x55\x07\x6b\xb8\xad\x0d\xee\xad\x46\x31\xbe\x2c\xb8\x34\x33\x7c\xed\x94\x46\xd8\xaa\x94\x25\x57\xa6\x2f\x50\xce\x80\xf2\xbe\x43\xc1\x34\xe8\x56\x8c\x09\xef\x76\x60\x1d\x70\x8b\x10\x88\x18\x8c\x0d\xa8\x99\xc2\x11\xf2\xb2\x2e\x5e\xb4\xe9\xac\xfb\x09\x01\xbb\xba\x88\x7c\xec\x30\xb6\x88\xa9\x97\x36\xe0\x76\x39\xa9\x7a\x65\x5d\xa9\x63\xda\xfa\x85\xac\x96\x1d\xcb\x0d\x99\x23\xb8\x9d\x48\x37\xd7\x45\xde\xc9\xbc\x92\x55\xaf\x6f\x0c\x3a\x5f\x29\xfd\xa2\xe4\x66\xac\x6f\x13\xfe\x58\x34\x3f\x68\x00\x15\x10\x86\x68\x53\xe3\xca\x81\x8c\x1c\xc8\xed\x1a\x1a\xd8\x28\x46\x23\xab\xf9\x00\x26\x5c\x28\xd3\x26\x50\x45\x04\xa9\xe6\x76\xb3\xa6\x9f\xaa\x6a\xc5\x5b\x6a\xea\xab\xa2\x19\xfc\x2e\x28\x83\x70\xa4\x21\x2c\x70\x59\xa9\x06\x98\xc0\xf6\x3e\xd0\x7e\xce\xe1\x6f\x8f\xc1\xa2\xd3\x58\xca\xca\x2f\x83\xac\x4c\x76\x62\xb4\x9b\xd7\xd1\x26\xc9\x21\x06\x9d\xa6\xa3\x03\x86\xa7\x74\xbf\x27\x97\x54\x8d\x15\xc6\xff\x45\x6c\x6d\xff\x12\x94\xd9\xc7\x69\xac\x11\x74\x1e\xc7\x73\x24\xf7\x5f\xd5\xd9\xcd\x14\x95\x7d\x12\xe6\x23\xcb\xe9\x8e\xe1\xd6\x98\x69\xca\x68\x19\x81\xc2\xe8\x19\xab\x55\xf6\xe8\x62\x3e\x68\x31\x89\x30\xfd\x9c\x12\xcc\xd8\x3d\x58\x93\xe5\x35\x4f\xf3\xde\x8a\xac\x79\x36\xed\x24\xfa\xa2\x76\x95\x4a\x9b\x57\x57\xad\xa7\x98\xe2\x38\xeb\x12\x44\x4f\x66\x48\xb6\x7a\xd7\xf8\x79\xd8\xf9\xfd\x1d\x56\x56\xd9\x8c\xa3\x3b\xf3\x9f\xe3\x9f\x00\x00\x00\xff\xff\x4a\x5c\x90\x9e\x2c\x05\x00\x00"
// browser_html returns raw, uncompressed file data.
func browser_html() []byte {
var empty [0]byte
sx := (*reflect.StringHeader)(unsafe.Pointer(&_browser_html))
b := empty[:]
bx := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bx.Data = sx.Data
bx.Len = len(_browser_html)
bx.Cap = bx.Len
gz, err := gzip.NewReader(bytes.NewBuffer(b))
if err != nil {
panic("Decompression failed: " + err.Error())
}
var buf bytes.Buffer
io.Copy(&buf, gz)
gz.Close()
return buf.Bytes()
}
func init() {
go_bindata["/browser.html"] = browser_html
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,39 +0,0 @@
package resources
import (
"bytes"
"compress/gzip"
"io"
"reflect"
"unsafe"
)
var _stats_html = "\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8c\x54\xcb\x6e\xdb\x30\x10\xbc\xe7\x2b\x36\xba\xd3\x3c\x14\x45\x92\x82\x12\x90\x16\x39\xe4\x56\xa0\x28\xd0\x22\x08\x02\x9a\x5c\x5b\x4c\x25\x2e\x4b\xae\xec\xfa\xef\x4b\x4a\x56\xa2\x3c\x5a\xd8\x07\x6b\xc5\xe5\xcc\x3e\x66\x6c\x75\x6e\xc9\xf0\x21\x20\xb4\xdc\x77\xcd\x99\x3a\x17\xe2\xce\x6d\xa0\x63\xb8\xbd\x81\x8b\xfb\x06\xc6\x8f\x2a\x59\x30\x9d\x4e\xa9\xae\x3c\x89\xc7\x94\x6f\x08\x87\x57\xd3\xe3\x72\x7a\x5c\x54\x0d\xa8\xf3\x3b\xf4\xd6\x6d\xee\x85\x78\x66\x5b\x52\x9d\xc0\xf6\x1f\x9a\xcb\x53\x68\xfe\x85\xdf\xf2\x91\xa2\x1c\x34\xef\xe0\x47\xa0\x10\x2f\xc0\xa5\x0e\x6a\x5b\x82\x1c\xf6\xc8\x1a\x4c\xab\x63\x42\xae\xab\x81\x37\x22\x77\xbb\x48\xb5\xcc\x41\xe0\xef\xc1\xed\xea\xea\x87\xf8\x7e\x2d\xbe\x50\x1f\x34\xbb\x75\x87\x15\x18\xf2\x8c\x3e\xe3\x6e\x6f\x6a\xb4\x5b\x9c\x91\xec\xb8\xc3\x06\xd9\x58\xf8\x1c\x69\x9f\x30\x2a\x39\x9d\x2d\x98\xbd\xee\xb1\xae\x2c\x26\x13\x5d\x60\x47\x7e\xc1\x57\xbd\xbd\xb8\x73\xb8\x0f\x14\x79\x71\x6b\xef\x2c\xb7\xb5\xc5\x9d\x33\x28\xc6\x97\x19\x97\x67\x86\xaf\x9d\x36\x08\x1b\x9d\xb3\xe4\x57\xf9\x0b\xb4\xb7\xa0\x43\xe8\x50\x30\x0d\xa6\x15\x63\x22\xf8\x2d\x38\x0f\xdc\x22\x44\x22\x06\xeb\x22\x1a\xa6\x78\x80\xb2\xac\xb3\x27\x6d\x3a\xe7\x7f\x41\xc4\xae\xae\x12\x1f\x3a\x4c\x2d\x62\xee\xa5\x8d\xb8\x99\x4f\x64\xaf\x9d\x5f\x99\x94\xb7\x7e\xa6\xe4\xbc\x63\xb5\x26\x7b\x00\xbf\x15\xb9\x72\x5d\x95\x9d\x7c\x63\xcd\x69\xd1\xe9\x0b\x7b\x1e\x0b\xaa\x30\xeb\xb8\x1e\x17\xd8\x66\xf4\xa1\x6a\x7e\xd2\x00\x3a\x22\x0c\xc9\xe5\xb6\xb5\x07\x95\x38\x92\xdf\x36\x34\xb0\xd5\x8c\x56\xc9\xe3\x01\x4c\xb8\xb8\xca\x7b\x40\x9d\x10\x94\x3e\x36\x5b\x14\xfd\x24\xe5\x82\x77\x65\xa8\x97\x55\x33\x84\x6d\xd4\x16\xe1\x40\x43\x9c\xe1\x4a\xea\x06\x98\xc0\xf5\x21\xd2\xee\x98\xc3\x3f\x01\xa3\x43\x6f\x70\xa5\x64\x98\x07\x59\x58\xec\x9d\xd1\xae\x9e\x47\x9b\x04\x87\x14\x4d\x9e\x8e\xf6\x18\x1f\x72\xfd\x40\x3e\x6b\x9a\x24\xa6\x8f\x22\xb5\xae\x7f\x0a\x56\xc5\xc5\x79\xac\x11\x74\x1a\xc7\x63\x22\xff\x41\x76\x6e\x3d\x45\xab\x3e\xcb\xf2\x96\xe5\xfd\x8e\xe1\xda\xda\x69\xca\xe4\x18\x81\xe2\xe8\x18\x67\x74\x71\xe8\x6c\x3d\x68\x31\x8b\x30\xfd\x98\x32\xcc\xba\x1d\x38\x5b\xc4\xb5\x0f\x69\x14\xb7\xe8\x5d\x0c\x3b\x09\x3e\x6b\x2d\xf3\xc5\xe6\xd9\x51\xcb\x19\xa6\x38\xc9\x11\x2e\x7a\xb2\x43\x36\xd4\xab\xa6\x4f\x41\x1e\xdf\x5e\x21\x95\x2c\x26\x1c\x5d\x59\xfe\x14\xff\x06\x00\x00\xff\xff\x95\x89\x83\x4d\x24\x05\x00\x00"
// stats_html returns raw, uncompressed file data.
func stats_html() []byte {
var empty [0]byte
sx := (*reflect.StringHeader)(unsafe.Pointer(&_stats_html))
b := empty[:]
bx := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bx.Data = sx.Data
bx.Len = len(_stats_html)
bx.Cap = bx.Len
gz, err := gzip.NewReader(bytes.NewBuffer(b))
if err != nil {
panic("Decompression failed: " + err.Error())
}
var buf bytes.Buffer
io.Copy(&buf, gz)
gz.Close()
return buf.Bytes()
}
func init() {
go_bindata["/stats.html"] = stats_html
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,39 +0,0 @@
package resources
import (
"bytes"
"compress/gzip"
"io"
"reflect"
"unsafe"
)
var _views_stats_html = "\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xd4\x56\x4b\x8f\xdb\x36\x10\xbe\xe7\x57\x10\xec\xc1\x09\x20\xc9\x7a\xf8\xb1\x32\x2c\x03\x4d\x0b\xf4\xd2\x14\x45\x53\x14\x28\x8a\x1e\x68\x8a\xb6\x88\xa5\x29\x95\xa4\xad\x75\x5c\xff\xf7\x0e\x45\xd9\x8d\x64\x79\xbb\x41\xf7\x92\x7d\x80\xe2\x70\xe6\x9b\x8f\xc3\x6f\x28\x2d\x73\x7e\x40\x54\x10\xad\x33\xcc\x0c\xcd\x7d\x5a\x4a\x43\xb8\x64\x0a\x35\x53\x6d\x88\xd1\xe8\x74\xa2\xa5\xd8\xef\xa4\x3e\x9f\xe1\xd9\x90\xb5\x60\xbf\x71\xcd\xd7\x5c\x70\x73\x3c\x9f\xf1\xea\x0d\x82\x9f\x1b\xac\x75\x99\x1f\xdb\xb5\xc1\xf5\x4d\xa9\x76\xc4\xf8\x9a\x09\x46\x4d\xa9\x3e\x73\x1d\x74\xbf\xf8\xf9\xdc\xb0\x1d\xea\x9a\xb6\x8a\x54\x05\x46\x72\xeb\x53\xc1\xe9\x63\x86\x75\x51\xd6\x3f\x58\xe3\xdb\x77\x3d\xdc\x06\x5b\x1f\xb6\xe8\xc0\x94\xe6\xa5\xcc\x70\x14\x44\x18\x3d\xed\x84\x84\x4c\x85\x31\xd5\x62\x3c\xae\xeb\x3a\xa8\x93\xa0\x54\xdb\x71\x1c\x86\xe1\x18\xfc\x5b\x97\xc5\x93\xe0\xf2\x71\xc8\x31\x4a\xd3\x74\xdc\xac\x82\x6b\x86\xc3\xea\x09\xa3\xa3\x1b\x6f\x08\x54\x8a\x69\xa6\x0e\xec\x5b\x5d\xc1\x06\x7e\x21\x86\x97\x19\x7e\xfa\xc0\xe5\xef\xf0\x8f\xd1\x81\xb3\xfa\x7d\x69\x41\x50\x88\x66\xf6\x2f\x08\xc3\x39\x46\x4c\xda\xda\xfb\x6b\x42\x1f\xb7\xaa\xdc\xcb\x3c\xc3\x92\xd5\xa8\xe7\x05\x3c\x17\xba\x22\x94\x65\xf8\x92\x67\xa8\x06\x15\x31\x05\xda\x70\x21\x32\xfc\x4d\xf4\x3d\xfc\xbe\xc7\x08\x00\x3f\x24\xa1\x0f\x40\x69\x44\xfd\x68\x16\xcc\xe2\xc8\x0b\xfd\xc4\x1a\x26\x5e\x94\x04\x93\xf9\xe4\x32\x73\x03\x0d\xbd\xd6\xcd\xad\x7a\x9d\xd5\x76\xb8\x49\xae\x9d\xdd\xef\xc4\xb4\xc0\xdf\xcd\xae\xd9\x92\x87\xc4\x9b\x34\xe8\x8e\x92\x77\xe1\xf6\x09\x01\x4d\x6f\x32\x0f\xe2\x24\xa5\x7e\x1a\x4c\xa3\x14\x68\x46\x76\x3e\xf5\xe7\xc1\x3c\x9a\x5d\x26\x6e\xb8\x21\xf0\x31\x0e\x83\xc9\x03\x90\x8e\x83\xf9\xec\x01\x70\xdb\x27\xda\x62\x79\x2e\xce\x6b\xb0\x2e\x13\x37\x7c\x4c\x9c\x8f\xcb\xee\x5d\x79\x7c\xc2\xe3\x81\x2a\x5b\xe9\xac\xde\x74\x95\x3d\x06\x69\xff\x0f\xb1\x37\x0d\xd8\x17\xfb\xaf\xd6\xf8\xf5\x8b\x7d\x9a\x06\x93\x74\x66\x87\x69\x98\x3c\x2b\xf8\x9e\xe7\x0b\x45\xaf\x80\x41\x43\xd8\xd1\xc5\xfd\x06\xa8\x79\x6e\x8a\x0c\x3b\x70\x8c\x0a\xc6\xb7\x85\x81\xaa\x25\x41\x14\x45\x83\x07\xdc\x45\x8c\xc1\xd1\x06\xbe\x32\x2c\x34\x41\x92\xc6\xaf\x00\xeb\xe4\xf8\x8c\x1a\xfb\xd3\xbe\x30\xdd\x4d\xdb\x83\x28\xe2\xd5\xcf\x0c\xde\x19\x3f\x12\xc3\x24\x3d\x2e\xc7\x60\x78\x5e\xdf\x0d\xcc\xbf\x6f\x1b\x8c\x38\x1c\xad\x70\xe1\x7d\xf4\x2f\x23\x28\xb8\x36\x77\xf9\xc1\xda\x00\xb9\xa6\xa1\x10\x65\x42\x54\x24\xcf\xb9\xdc\x36\x65\xb7\x73\xab\xa8\x76\xde\x8f\x29\x18\xc9\x07\x0a\x6c\xf2\x0e\x1b\x49\x76\xcc\xb7\xae\xb0\x47\xc7\xe1\x27\xb0\x2c\xc7\xe6\x05\xb1\xd7\x72\x5c\xcb\xda\x8f\x02\xcb\x2d\x8d\xa5\xb1\x2f\xde\x21\x78\x65\xef\x0c\xc5\x2a\x46\x40\x24\x95\x25\xc3\x25\xb2\xa3\x1e\x68\x95\x0b\x23\x08\xd1\x35\x37\xb4\x40\xf6\xfa\x38\x19\xb5\x67\x8b\x91\x68\x76\x34\xf2\xd0\x86\x08\xcd\x16\x68\xb4\x29\x85\x28\x6b\x30\x9d\xff\x70\x6b\x76\x9b\x28\xcb\x1a\xf8\xc0\x56\xe1\xcf\x3b\x39\x9a\x3c\xf6\x10\xaf\x89\xfc\xba\x60\x90\x4a\xb4\x55\x3b\x9d\xae\x18\xe7\xf3\x12\x4e\x44\x76\xaa\x64\x17\x7d\x73\xac\xa0\xdd\xdf\xba\x90\x77\xa0\x72\xf0\x5a\x0d\xdc\xb3\xcf\x24\xcd\xd9\x86\xec\x85\xe9\xa5\xbb\x0f\x31\x7c\x86\x6d\xd5\xfe\x23\x6b\xe7\xa6\xff\x6b\x4f\x14\x73\xb7\x39\x58\x17\xe8\x34\xfa\xcc\x0e\x8d\xc2\x98\x1c\x2d\x5c\x1d\x5b\x45\x04\x74\xaf\x14\x93\x06\x2d\x51\x3c\xf5\x50\xc7\xbf\x54\x44\x6e\xd9\xfd\x80\x59\xd8\x0b\x50\x2c\xbf\xe7\xbd\xca\xc0\x1d\x3e\xef\x5e\x52\xc8\x01\xe1\xfa\x07\x22\xf6\xec\x7a\x80\x7d\xf4\xbf\x91\xdc\xef\xd6\x4c\x2d\x22\x04\xdf\x95\x3b\xfd\xc5\xb5\x06\xab\xba\xe9\x87\x5b\xed\x83\xd1\xf6\xf7\xe0\x0d\xd2\x3e\xb6\xc3\x3f\x01\x00\x00\xff\xff\x0a\x0c\x99\x1a\x0e\x0b\x00\x00"
// views_stats_html returns raw, uncompressed file data.
func views_stats_html() []byte {
var empty [0]byte
sx := (*reflect.StringHeader)(unsafe.Pointer(&_views_stats_html))
b := empty[:]
bx := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bx.Data = sx.Data
bx.Len = len(_views_stats_html)
bx.Cap = bx.Len
gz, err := gzip.NewReader(bytes.NewBuffer(b))
if err != nil {
panic("Decompression failed: " + err.Error())
}
var buf bytes.Buffer
io.Copy(&buf, gz)
gz.Close()
return buf.Bytes()
}
func init() {
go_bindata["/views/stats.html"] = views_stats_html
}

View File

@ -0,0 +1,45 @@
package v2
import (
"fmt"
"io"
"net/http"
"net/url"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
)
// deleteHandler remove a given leader.
func (h *handler) deleteHandler(w http.ResponseWriter, req *http.Request) error {
vars := mux.Vars(req)
name := req.FormValue("name")
if name == "" {
return etcdErr.NewError(etcdErr.EcodeNameRequired, "Delete", 0)
}
// Proxy the request to the the lock service.
u, err := url.Parse(fmt.Sprintf("%s/mod/v2/lock/%s", h.addr, vars["key"]))
if err != nil {
return err
}
q := u.Query()
q.Set("value", name)
u.RawQuery = q.Encode()
r, err := http.NewRequest("DELETE", u.String(), nil)
if err != nil {
return err
}
// Read from the leader lock.
resp, err := h.client.Do(r)
if err != nil {
return err
}
defer resp.Body.Close()
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
return nil
}

View File

@ -0,0 +1,26 @@
package v2
import (
"fmt"
"io"
"net/http"
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
)
// getHandler retrieves the current leader.
func (h *handler) getHandler(w http.ResponseWriter, req *http.Request) error {
vars := mux.Vars(req)
// Proxy the request to the lock service.
url := fmt.Sprintf("%s/mod/v2/lock/%s?field=value", h.addr, vars["key"])
resp, err := h.client.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
return nil
}

49
mod/leader/v2/handler.go Normal file
View File

@ -0,0 +1,49 @@
package v2
import (
"net/http"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
)
// prefix is appended to the lock's prefix since the leader mod uses the lock mod.
const prefix = "/_mod/leader"
// handler manages the leader HTTP request.
type handler struct {
*mux.Router
client *http.Client
transport *http.Transport
addr string
}
// NewHandler creates an HTTP handler that can be registered on a router.
func NewHandler(addr string) http.Handler {
transport := &http.Transport{DisableKeepAlives: false}
h := &handler{
Router: mux.NewRouter(),
client: &http.Client{Transport: transport},
transport: transport,
addr: addr,
}
h.StrictSlash(false)
h.handleFunc("/{key:.*}", h.getHandler).Methods("GET")
h.handleFunc("/{key:.*}", h.setHandler).Methods("PUT")
h.handleFunc("/{key:.*}", h.deleteHandler).Methods("DELETE")
return h
}
func (h *handler) handleFunc(path string, f func(http.ResponseWriter, *http.Request) error) *mux.Route {
return h.Router.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
if err := f(w, req); err != nil {
switch err := err.(type) {
case *etcdErr.Error:
w.Header().Set("Content-Type", "application/json")
err.Write(w)
default:
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
})
}

View File

@ -0,0 +1,58 @@
package v2
import (
"fmt"
"io"
"net/http"
"net/url"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
)
// setHandler attempts to set the current leader.
func (h *handler) setHandler(w http.ResponseWriter, req *http.Request) error {
vars := mux.Vars(req)
name := req.FormValue("name")
if name == "" {
return etcdErr.NewError(etcdErr.EcodeNameRequired, "Set", 0)
}
// Proxy the request to the the lock service.
u, err := url.Parse(fmt.Sprintf("%s/mod/v2/lock/%s", h.addr, vars["key"]))
if err != nil {
return err
}
q := u.Query()
q.Set("value", name)
q.Set("ttl", req.FormValue("ttl"))
q.Set("timeout", req.FormValue("timeout"))
u.RawQuery = q.Encode()
r, err := http.NewRequest("POST", u.String(), nil)
if err != nil {
return err
}
// Close request if this connection disconnects.
closeNotifier, _ := w.(http.CloseNotifier)
stopChan := make(chan bool)
defer close(stopChan)
go func() {
select {
case <-closeNotifier.CloseNotify():
h.transport.CancelRequest(r)
case <-stopChan:
}
}()
// Read from the leader lock.
resp, err := h.client.Do(r)
if err != nil {
return err
}
defer resp.Body.Close()
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
return nil
}

View File

@ -0,0 +1,85 @@
package leader
import (
"fmt"
"testing"
"time"
"github.com/coreos/etcd/server"
"github.com/coreos/etcd/tests"
"github.com/coreos/etcd/third_party/github.com/stretchr/testify/assert"
)
// Ensure that a leader can be set and read.
func TestModLeaderSet(t *testing.T) {
tests.RunServer(func(s *server.Server) {
// Set leader.
body, status, err := testSetLeader(s, "foo", "xxx", 10)
assert.NoError(t, err)
assert.Equal(t, status, 200)
assert.Equal(t, body, "2")
// Check that the leader is set.
body, status, err = testGetLeader(s, "foo")
assert.NoError(t, err)
assert.Equal(t, status, 200)
assert.Equal(t, body, "xxx")
// Delete leader.
body, status, err = testDeleteLeader(s, "foo", "xxx")
assert.NoError(t, err)
assert.Equal(t, status, 200)
assert.Equal(t, body, "")
// Check that the leader is removed.
body, status, err = testGetLeader(s, "foo")
assert.NoError(t, err)
assert.Equal(t, status, 200)
assert.Equal(t, body, "")
})
}
// Ensure that a leader can be renewed.
func TestModLeaderRenew(t *testing.T) {
tests.RunServer(func(s *server.Server) {
// Set leader.
body, status, err := testSetLeader(s, "foo", "xxx", 2)
assert.NoError(t, err)
assert.Equal(t, status, 200)
assert.Equal(t, body, "2")
time.Sleep(1 * time.Second)
// Renew leader.
body, status, err = testSetLeader(s, "foo", "xxx", 3)
assert.NoError(t, err)
assert.Equal(t, status, 200)
assert.Equal(t, body, "2")
time.Sleep(2 * time.Second)
// Check that the leader is set.
body, status, err = testGetLeader(s, "foo")
assert.NoError(t, err)
assert.Equal(t, status, 200)
assert.Equal(t, body, "xxx")
})
}
func testSetLeader(s *server.Server, key string, name string, ttl int) (string, int, error) {
resp, err := tests.PutForm(fmt.Sprintf("%s/mod/v2/leader/%s?name=%s&ttl=%d", s.URL(), key, name, ttl), nil)
ret := tests.ReadBody(resp)
return string(ret), resp.StatusCode, err
}
func testGetLeader(s *server.Server, key string) (string, int, error) {
resp, err := tests.Get(fmt.Sprintf("%s/mod/v2/leader/%s", s.URL(), key))
ret := tests.ReadBody(resp)
return string(ret), resp.StatusCode, err
}
func testDeleteLeader(s *server.Server, key string, name string) (string, int, error) {
resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/v2/leader/%s?name=%s", s.URL(), key, name), nil)
ret := tests.ReadBody(resp)
return string(ret), resp.StatusCode, err
}

View File

@ -1,128 +1,181 @@
package v2
import (
"errors"
"fmt"
"net/http"
"path"
"strconv"
"time"
"github.com/coreos/go-etcd/etcd"
"github.com/gorilla/mux"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
)
// acquireHandler attempts to acquire a lock on the given key.
// The "key" parameter specifies the resource to lock.
// The "value" parameter specifies a value to associate with the lock.
// The "ttl" parameter specifies how long the lock will persist for.
// The "timeout" parameter specifies how long the request should wait for the lock.
func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) {
func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) error {
h.client.SyncCluster()
// Setup connection watcher.
closeNotifier, _ := w.(http.CloseNotifier)
closeChan := closeNotifier.CloseNotify()
stopChan := make(chan bool)
// Parse "key" and "ttl" query parameters.
// Parse the lock "key".
vars := mux.Vars(req)
keypath := path.Join(prefix, vars["key"])
ttl, err := strconv.Atoi(req.FormValue("ttl"))
if err != nil {
http.Error(w, "invalid ttl: " + err.Error(), http.StatusInternalServerError)
return
}
value := req.FormValue("value")
// Parse "timeout" parameter.
var timeout int
if len(req.FormValue("timeout")) == 0 {
var err error
if req.FormValue("timeout") == "" {
timeout = -1
} else if timeout, err = strconv.Atoi(req.FormValue("timeout")); err != nil {
http.Error(w, "invalid timeout: " + err.Error(), http.StatusInternalServerError)
return
return etcdErr.NewError(etcdErr.EcodeTimeoutNaN, "Acquire", 0)
}
timeout = timeout + 1
// Create an incrementing id for the lock.
resp, err := h.client.AddChild(keypath, "-", uint64(ttl))
// Parse TTL.
ttl, err := strconv.Atoi(req.FormValue("ttl"))
if err != nil {
http.Error(w, "add lock index error: " + err.Error(), http.StatusInternalServerError)
return
return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Acquire", 0)
}
// If node exists then just watch it. Otherwise create the node and watch it.
node, index, pos := h.findExistingNode(keypath, value)
if index > 0 {
if pos == 0 {
// If lock is already acquired then update the TTL.
h.client.Update(node.Key, node.Value, uint64(ttl))
} else {
// Otherwise watch until it becomes acquired (or errors).
err = h.watch(keypath, index, nil)
}
} else {
index, err = h.createNode(keypath, value, ttl, closeChan, stopChan)
}
// Stop all goroutines.
close(stopChan)
// Check for an error.
if err != nil {
return err
}
// Write response.
w.Write([]byte(strconv.Itoa(index)))
return nil
}
// createNode creates a new lock node and watches it until it is acquired or acquisition fails.
func (h *handler) createNode(keypath string, value string, ttl int, closeChan <-chan bool, stopChan chan bool) (int, error) {
// Default the value to "-" if it is blank.
if len(value) == 0 {
value = "-"
}
// Create an incrementing id for the lock.
resp, err := h.client.AddChild(keypath, value, uint64(ttl))
if err != nil {
return 0, err
}
indexpath := resp.Node.Key
index, _ := strconv.Atoi(path.Base(indexpath))
// Keep updating TTL to make sure lock request is not expired before acquisition.
stop := make(chan bool)
go h.ttlKeepAlive(indexpath, ttl, stop)
go h.ttlKeepAlive(indexpath, value, ttl, stopChan)
// Monitor for broken connection.
// Watch until we acquire or fail.
err = h.watch(keypath, index, closeChan)
// Check for connection disconnect before we write the lock index.
if err != nil {
select {
case <-closeChan:
err = errors.New("user interrupted")
default:
}
}
// Update TTL one last time if acquired. Otherwise delete.
if err == nil {
h.client.Update(indexpath, value, uint64(ttl))
} else {
h.client.Delete(indexpath, false)
}
return index, err
}
// findExistingNode search for a node on the lock with the given value.
func (h *handler) findExistingNode(keypath string, value string) (*etcd.Node, int, int) {
if len(value) > 0 {
resp, err := h.client.Get(keypath, true, true)
if err == nil {
nodes := lockNodes{resp.Node.Nodes}
if node, pos := nodes.FindByValue(value); node != nil {
index, _ := strconv.Atoi(path.Base(node.Key))
return node, index, pos
}
}
}
return nil, 0, 0
}
// ttlKeepAlive continues to update a key's TTL until the stop channel is closed.
func (h *handler) ttlKeepAlive(k string, value string, ttl int, stopChan chan bool) {
for {
select {
case <-time.After(time.Duration(ttl/2) * time.Second):
h.client.Update(k, value, uint64(ttl))
case <-stopChan:
return
}
}
}
// watch continuously waits for a given lock index to be acquired or until lock fails.
// Returns a boolean indicating success.
func (h *handler) watch(keypath string, index int, closeChan <-chan bool) error {
// Wrap close chan so we can pass it to Client.Watch().
stopWatchChan := make(chan bool)
go func() {
select {
case <-closeChan:
stopWatchChan <- true
case <-stop:
// Stop watching for connection disconnect.
case <-stopWatchChan:
}
}()
defer close(stopWatchChan)
// Extract the lock index.
index, _ := strconv.Atoi(path.Base(resp.Node.Key))
// Wait until we successfully get a lock or we get a failure.
var success bool
for {
// Read all indices.
resp, err = h.client.Get(keypath, true, true)
// Read all nodes for the lock.
resp, err := h.client.Get(keypath, true, true)
if err != nil {
http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError)
break
return fmt.Errorf("lock watch lookup error: %s", err.Error())
}
indices := extractResponseIndices(resp)
waitIndex := resp.Node.ModifiedIndex
prevIndex := findPrevIndex(indices, index)
nodes := lockNodes{resp.Node.Nodes}
prevIndex := nodes.PrevIndex(index)
// If there is no previous index then we have the lock.
if prevIndex == 0 {
success = true
break
return nil
}
// Otherwise watch previous index until it's gone.
// Watch previous index until it's gone.
_, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), waitIndex, false, nil, stopWatchChan)
if err == etcd.ErrWatchStoppedByUser {
break
return fmt.Errorf("lock watch closed")
} else if err != nil {
http.Error(w, "lock watch error: " + err.Error(), http.StatusInternalServerError)
break
}
}
// Check for connection disconnect before we write the lock index.
select {
case <-stopWatchChan:
success = false
default:
}
// Stop the ttl keep-alive.
close(stop)
if success {
// Write lock index to response body if we acquire the lock.
h.client.Update(indexpath, "-", uint64(ttl))
w.Write([]byte(strconv.Itoa(index)))
} else {
// Make sure key is deleted if we couldn't acquire.
h.client.Delete(indexpath, false)
}
}
// ttlKeepAlive continues to update a key's TTL until the stop channel is closed.
func (h *handler) ttlKeepAlive(k string, ttl int, stop chan bool) {
for {
select {
case <-time.After(time.Duration(ttl / 2) * time.Second):
h.client.Update(k, "-", uint64(ttl))
case <-stop:
return
return fmt.Errorf("lock watch error: %s", err.Error())
}
}
}

View File

@ -3,28 +3,43 @@ package v2
import (
"net/http"
"path"
"strconv"
"github.com/gorilla/mux"
etcdErr "github.com/coreos/etcd/error"
"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
)
// getIndexHandler retrieves the current lock index.
func (h *handler) getIndexHandler(w http.ResponseWriter, req *http.Request) {
// The "field" parameter specifies to read either the lock "index" or lock "value".
func (h *handler) getIndexHandler(w http.ResponseWriter, req *http.Request) error {
h.client.SyncCluster()
vars := mux.Vars(req)
keypath := path.Join(prefix, vars["key"])
field := req.FormValue("field")
if len(field) == 0 {
field = "value"
}
// Read all indices.
resp, err := h.client.Get(keypath, true, true)
if err != nil {
http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError)
return
return err
}
nodes := lockNodes{resp.Node.Nodes}
// Write out the requested field.
if node := nodes.First(); node != nil {
switch field {
case "index":
w.Write([]byte(path.Base(node.Key)))
case "value":
w.Write([]byte(node.Value))
default:
return etcdErr.NewError(etcdErr.EcodeInvalidField, "Get", 0)
}
}
// Write out the index of the last one to the response body.
indices := extractResponseIndices(resp)
if len(indices) > 0 {
w.Write([]byte(strconv.Itoa(indices[0])))
}
return nil
}

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