From 1a21de143f46d52fc4705a7eb7e5612a6432e2f2 Mon Sep 17 00:00:00 2001 From: JChien Date: Wed, 17 Dec 2014 02:37:25 +0800 Subject: [PATCH 1/6] add consul support as a disovery backend Signed-off-by: JChien --- discovery/README.md | 27 +++++++++++ discovery/consul/consul.go | 86 +++++++++++++++++++++++++++++++++ discovery/consul/consul_test.go | 13 +++++ flags.go | 2 +- main.go | 1 + 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 discovery/consul/consul.go create mode 100644 discovery/consul/consul_test.go diff --git a/discovery/README.md b/discovery/README.md index e93490ac45..11723047c8 100644 --- a/discovery/README.md +++ b/discovery/README.md @@ -78,6 +78,33 @@ $ swarm list --discovery etcd:/// http:// ``` +###### Using consul + +```bash +# create a cluster +$ swarm create +6856663cdefdec325839a4b7e1de38e8 + +# on each of your nodes, start the swarm agent +# doesn't have to be public (eg. 192.168.0.X), +# as long as the other nodes can reach it, it is fine. +$ swarm join --discovery consul:///>6856663cdefdec325839a4b7e1de38e8> --addr= + +# start the manager on any machine or your laptop +$ swarm manage --discovery consul:///>6856663cdefdec325839a4b7e1de38e8> --addr= + +# use the regular docker cli +$ docker -H info +$ docker -H run ... +$ docker -H ps +$ docker -H logs ... +... + +# list nodes in your cluster +$ swarm list --discovery consul:///>path> +http:// +``` + ## Contributing Contributing a new discovery backend is easy, diff --git a/discovery/consul/consul.go b/discovery/consul/consul.go new file mode 100644 index 0000000000..591fbe9952 --- /dev/null +++ b/discovery/consul/consul.go @@ -0,0 +1,86 @@ +package consul + +import ( + "errors" + "path" + "strings" + "time" + + consul "github.com/armon/consul-api" + "github.com/docker/swarm/discovery" +) + +type ConsulDiscoveryService struct { + heartbeat uint64 + client *consul.Client + prefix string +} + +const swarm_prefix = "swarm" + +func init() { + discovery.Register("consul", &ConsulDiscoveryService{}) +} + +func (s *ConsulDiscoveryService) Initialize(uris string, heartbeat int) error { + parts := strings.SplitN(uris, "/", 2) + if len(parts) < 2 { + return errors.New("missing consul prefix") + } + addr := parts[0] + path := parts[1] + + config := consul.DefaultConfig() + config.Address = addr + + client, err := consul.NewClient(config) + if err != nil { + return err + } + s.client = client + s.heartbeat = uint64(heartbeat) + s.prefix = swarm_prefix + "/" + path + "/" + kv := s.client.KV() + p := &consul.KVPair{Key: s.prefix, Value: nil} + _, err = kv.Put(p, nil) + if err != nil { + return err + } + return nil +} +func (s *ConsulDiscoveryService) Fetch() ([]*discovery.Node, error) { + kv := s.client.KV() + pairs, _, err := kv.List(s.prefix, nil) + if err != nil { + return nil, err + } + + var nodes []*discovery.Node + + for _, pair := range pairs { + if pair.Key == s.prefix { + continue + } + nodes = append(nodes, discovery.NewNode(string(pair.Value))) + } + return nodes, nil +} + +func (s *ConsulDiscoveryService) Watch(callback discovery.WatchCallback) { + for _ = range time.Tick(time.Duration(s.heartbeat) * time.Second) { + nodes, err := s.Fetch() + if err == nil { + callback(nodes) + } + } +} + +func (s *ConsulDiscoveryService) Register(addr string) error { + kv := s.client.KV() + p := &consul.KVPair{Key: path.Join(s.prefix, addr), Value: []byte(addr)} + _, err := kv.Put(p, nil) + if err != nil { + return err + } + return nil +} diff --git a/discovery/consul/consul_test.go b/discovery/consul/consul_test.go new file mode 100644 index 0000000000..d154b8f87c --- /dev/null +++ b/discovery/consul/consul_test.go @@ -0,0 +1,13 @@ +package consul + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInitialize(t *testing.T) { + discovery := &ConsulDiscoveryService{} + discovery.Initialize("127.0.0.1:8500/path", 0) + assert.Equal(t, discovery.prefix, "swarm/path/") +} diff --git a/flags.go b/flags.go index f357c3c276..8b92200d7c 100644 --- a/flags.go +++ b/flags.go @@ -6,7 +6,7 @@ var ( flDiscovery = cli.StringFlag{ Name: "discovery", Value: "", - Usage: "DiscoveryService to use [token://, etcd://,/, file://path/to/file]", + Usage: "DiscoveryService to use [token://, etcd://,/, file://path/to/file], consul:///", EnvVar: "SWARM_DISCOVERY", } flAddr = cli.StringFlag{ diff --git a/main.go b/main.go index cf524bdfba..4eb1d64a73 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "github.com/codegangsta/cli" "github.com/docker/swarm/discovery" + _ "github.com/docker/swarm/discovery/consul" _ "github.com/docker/swarm/discovery/etcd" _ "github.com/docker/swarm/discovery/file" "github.com/docker/swarm/discovery/token" From e0461c754985a7998025a64a41715fa1f2d99913 Mon Sep 17 00:00:00 2001 From: JChien Date: Wed, 17 Dec 2014 15:28:42 +0800 Subject: [PATCH 2/6] fix readme and remove swarm prefix Signed-off-by: JChien --- discovery/README.md | 10 +++------- discovery/consul/consul.go | 4 +--- discovery/consul/consul_test.go | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/discovery/README.md b/discovery/README.md index 11723047c8..235523adee 100644 --- a/discovery/README.md +++ b/discovery/README.md @@ -81,17 +81,13 @@ http:// ###### Using consul ```bash -# create a cluster -$ swarm create -6856663cdefdec325839a4b7e1de38e8 - # on each of your nodes, start the swarm agent # doesn't have to be public (eg. 192.168.0.X), # as long as the other nodes can reach it, it is fine. -$ swarm join --discovery consul:///>6856663cdefdec325839a4b7e1de38e8> --addr= +$ swarm join --discovery consul:/// --addr= # start the manager on any machine or your laptop -$ swarm manage --discovery consul:///>6856663cdefdec325839a4b7e1de38e8> --addr= +$ swarm manage --discovery consul:/// --addr= # use the regular docker cli $ docker -H info @@ -101,7 +97,7 @@ $ docker -H logs ... ... # list nodes in your cluster -$ swarm list --discovery consul:///>path> +$ swarm list --discovery consul:/// http:// ``` diff --git a/discovery/consul/consul.go b/discovery/consul/consul.go index 591fbe9952..b1c7883c9e 100644 --- a/discovery/consul/consul.go +++ b/discovery/consul/consul.go @@ -16,8 +16,6 @@ type ConsulDiscoveryService struct { prefix string } -const swarm_prefix = "swarm" - func init() { discovery.Register("consul", &ConsulDiscoveryService{}) } @@ -39,7 +37,7 @@ func (s *ConsulDiscoveryService) Initialize(uris string, heartbeat int) error { } s.client = client s.heartbeat = uint64(heartbeat) - s.prefix = swarm_prefix + "/" + path + "/" + s.prefix = path + "/" kv := s.client.KV() p := &consul.KVPair{Key: s.prefix, Value: nil} _, err = kv.Put(p, nil) diff --git a/discovery/consul/consul_test.go b/discovery/consul/consul_test.go index d154b8f87c..79bf873bdf 100644 --- a/discovery/consul/consul_test.go +++ b/discovery/consul/consul_test.go @@ -9,5 +9,5 @@ import ( func TestInitialize(t *testing.T) { discovery := &ConsulDiscoveryService{} discovery.Initialize("127.0.0.1:8500/path", 0) - assert.Equal(t, discovery.prefix, "swarm/path/") + assert.Equal(t, discovery.prefix, "path/") } From 6aed8aa673f00116bf42e04d0a80718e513b5e9d Mon Sep 17 00:00:00 2001 From: JChien Date: Thu, 18 Dec 2014 00:11:25 +0800 Subject: [PATCH 3/6] fix style to match gofmt Signed-off-by: JChien --- discovery/consul/consul.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/discovery/consul/consul.go b/discovery/consul/consul.go index b1c7883c9e..792e8cb381 100644 --- a/discovery/consul/consul.go +++ b/discovery/consul/consul.go @@ -65,7 +65,9 @@ func (s *ConsulDiscoveryService) Fetch() ([]*discovery.Node, error) { } func (s *ConsulDiscoveryService) Watch(callback discovery.WatchCallback) { - for _ = range time.Tick(time.Duration(s.heartbeat) * time.Second) { + c := time.Tick(time.Duration(s.heartbeat) * time.Second) + for { + <-c nodes, err := s.Fetch() if err == nil { callback(nodes) From f6b5ca38d5576dc54c8578f8e2ce51f16d2505b3 Mon Sep 17 00:00:00 2001 From: JChien Date: Fri, 19 Dec 2014 01:38:32 +0800 Subject: [PATCH 4/6] fix flag usage message Signed-off-by: JChien --- flags.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flags.go b/flags.go index 8b92200d7c..a734d9a3f9 100644 --- a/flags.go +++ b/flags.go @@ -6,7 +6,7 @@ var ( flDiscovery = cli.StringFlag{ Name: "discovery", Value: "", - Usage: "DiscoveryService to use [token://, etcd://,/, file://path/to/file], consul:///", + Usage: "DiscoveryService to use [token://, etcd://,/, file://path/to/file, consul:///]", EnvVar: "SWARM_DISCOVERY", } flAddr = cli.StringFlag{ From a94a104932e0940ed691bc6367fd25dd20e6946d Mon Sep 17 00:00:00 2001 From: JChien Date: Fri, 19 Dec 2014 01:47:28 +0800 Subject: [PATCH 5/6] use consul's watch Signed-off-by: JChien --- discovery/consul/consul.go | 42 +++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/discovery/consul/consul.go b/discovery/consul/consul.go index 792e8cb381..00290fa0dd 100644 --- a/discovery/consul/consul.go +++ b/discovery/consul/consul.go @@ -6,14 +6,16 @@ import ( "strings" "time" + log "github.com/Sirupsen/logrus" consul "github.com/armon/consul-api" "github.com/docker/swarm/discovery" ) type ConsulDiscoveryService struct { - heartbeat uint64 + heartbeat time.Duration client *consul.Client prefix string + lastIndex uint64 } func init() { @@ -36,14 +38,18 @@ func (s *ConsulDiscoveryService) Initialize(uris string, heartbeat int) error { return err } s.client = client - s.heartbeat = uint64(heartbeat) + s.heartbeat = time.Duration(heartbeat) * time.Second s.prefix = path + "/" kv := s.client.KV() p := &consul.KVPair{Key: s.prefix, Value: nil} - _, err = kv.Put(p, nil) + if _, err = kv.Put(p, nil); err != nil { + return err + } + _, meta, err := kv.Get(s.prefix, nil) if err != nil { return err } + s.lastIndex = meta.LastIndex return nil } func (s *ConsulDiscoveryService) Fetch() ([]*discovery.Node, error) { @@ -65,9 +71,7 @@ func (s *ConsulDiscoveryService) Fetch() ([]*discovery.Node, error) { } func (s *ConsulDiscoveryService) Watch(callback discovery.WatchCallback) { - c := time.Tick(time.Duration(s.heartbeat) * time.Second) - for { - <-c + for _ = range s.waitForChange() { nodes, err := s.Fetch() if err == nil { callback(nodes) @@ -79,8 +83,26 @@ func (s *ConsulDiscoveryService) Register(addr string) error { kv := s.client.KV() p := &consul.KVPair{Key: path.Join(s.prefix, addr), Value: []byte(addr)} _, err := kv.Put(p, nil) - if err != nil { - return err - } - return nil + return err +} + +func (s *ConsulDiscoveryService) waitForChange() <-chan uint64 { + c := make(chan uint64) + go func() { + for { + kv := s.client.KV() + option := &consul.QueryOptions{ + WaitIndex: s.lastIndex, + WaitTime: s.heartbeat} + _, meta, err := kv.List(s.prefix, option) + if err != nil { + log.Errorln(err) + break + } + s.lastIndex = meta.LastIndex + c <- s.lastIndex + } + close(c) + }() + return c } From cf42030741a273c7aa2dd8a700bb17ebd97eb3ea Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 31 Dec 2014 00:16:54 +0000 Subject: [PATCH 6/6] update README.md & ROADMAP.md Signed-off-by: Victor Vieux --- ROADMAP.md | 2 +- discovery/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 4627c6434a..b98bf16643 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -24,6 +24,6 @@ Docker Swarm Roadmap * [ ] Discovery backends * [x] etcd * [ ] zookeeper - * [ ] consul + * [x] consul * [x] hub * [x] file diff --git a/discovery/README.md b/discovery/README.md index 235523adee..5ed6656770 100644 --- a/discovery/README.md +++ b/discovery/README.md @@ -87,7 +87,7 @@ http:// $ swarm join --discovery consul:/// --addr= # start the manager on any machine or your laptop -$ swarm manage --discovery consul:/// --addr= +$ swarm manage --discovery consul:/// -H= # use the regular docker cli $ docker -H info